Zulip Chat Archive

Stream: general

Topic: Base case of recursive definition


Heather Macbeth (Sep 30 2022 at 03:53):

I'm used to being able to get out the base case of a recursive definition by rfl:

def two_pow :   
| 0 := 1
| (i + 1) := 2 * two_pow i

example : two_pow 0 = 1 := rfl

In the following example, it doesn't seem to work. Is there another way?

def pascal :     
| 0 j := 1
| i 0 := 1
| (i + 1) (j + 1) := pascal i (j + 1) + pascal (i + 1) j

example : pascal 0 0 = 1 := rfl -- fails

This seems pretty similar to a question from a couple of months ago, but I can't get the cases trick from there to work.

Adam Topaz (Sep 30 2022 at 04:42):

unfold pascal works, but this is indeed strange. I would have expected refl to work!

Mario Carneiro (Sep 30 2022 at 04:51):

it's proved by well founded recursion, those don't compute by rfl

Mario Carneiro (Sep 30 2022 at 04:58):

It is possible to restructure the definition to be structurally recursive, in which case all the theorems are by rfl (except the overlapping patterns)

def pascal_succ (f :   ) :   
| 0 := 1
| (j + 1) := f (j + 1) + pascal_succ j

def pascal :     
| 0 j := 1
| (i + 1) j := pascal_succ (pascal i) j

theorem zero_pascal (j) : pascal 0 j = 1 := rfl
theorem pascal_zero (i) : pascal i 0 = 1 := by cases i; refl
theorem pascal_succ_succ (i j) :
  pascal (i + 1) (j + 1) = pascal i (j + 1) + pascal (i + 1) j := rfl

Mario Carneiro (Sep 30 2022 at 04:59):

If you want to keep the original definition, you still can prove the equations using the generated equation lemmas:

def pascal :     
| 0 j := 1
| i 0 := 1
| (i + 1) (j + 1) := pascal i (j + 1) + pascal (i + 1) j

theorem zero_pascal (j) : pascal 0 j = 1 := by cases j; simp [pascal]
theorem pascal_zero (i) : pascal i 0 = 1 := by cases i; simp [pascal]
theorem pascal_succ_succ (i j) :
  pascal (i + 1) (j + 1) = pascal i (j + 1) + pascal (i + 1) j := by simp [pascal]

Kevin Buzzard (Sep 30 2022 at 06:48):

Rule of thumb: when there are two variables involved but you're only defining the answer in three cases not four, the "it's all rfl" model breaks down sometimes.

Heather Macbeth (Sep 30 2022 at 13:43):

Thanks all!

Adam Topaz (Sep 30 2022 at 14:30):

Kevin Buzzard said:

Rule of thumb: when there are two variables involved but you're only defining the answer in three cases not four, the "it's all rfl" model breaks down sometimes.

I'm not so sure about this....

def pascal :     
| 0 0 := 1
| 0 (j+1) := 1
| i 0 := 1
| (i + 1) (j + 1) := pascal i (j + 1) + pascal (i + 1) j

example : pascal 0 0 = 1 := rfl -- fails

Adam Topaz (Sep 30 2022 at 14:38):

it seems that the equation compiler still splits up the original definition into the four cases?!

import tactic

def pascal :     
| 0 j := 1
| i 0 := 1
| (i + 1) (j + 1) := pascal i (j + 1) + pascal (i + 1) j

#check pascal.equations._eqn_1
#check pascal.equations._eqn_2
#check pascal.equations._eqn_3
#check pascal.equations._eqn_4

Patrick Johnson (Sep 30 2022 at 14:52):

Adam Topaz said:

Kevin Buzzard said:

Rule of thumb: when there are two variables involved but you're only defining the answer in three cases not four, the "it's all rfl" model breaks down sometimes.

I'm not so sure about this....

Let P="two variables and three cases" and Q="it's all rfl". What Kevin asserts is that P → ¬Q (sometimes), which doesn't imply ¬P → Q.

Patrick Johnson (Sep 30 2022 at 14:53):

Although, there is an example of P → Q (two variables, three cases, but all-rfl works):

def f :     
| a 0 := 1
| a 1 := 2
| a (b+2) := 3

example {a : }   : f a 0 = 1     := rfl
example {a : }   : f a 1 = 2     := rfl
example {a b : } : f a (b+2) = 3 := rfl

Kyle Miller (Sep 30 2022 at 14:58):

@Adam Topaz I think the pascal function is demonstrating that the equation lemmas are more of a rule of thumb of what you might expect rfl to prove, but they're actually not always what rfl can prove! For example, unfold pascal works but dunfold pascal does not.

Kyle Miller (Sep 30 2022 at 14:59):

You can see the whole definition in all its well-founded glory with delta pascal pascal._main._pack

Adam Topaz (Sep 30 2022 at 15:00):

yeah I tried that too... it's terrible. But I guess this is a good reminder that unfold doesn't necessarily preserve definitional equality!

Adam Topaz (Sep 30 2022 at 15:01):

How is pascal.equations._eqn_1 proved under the hood?

Floris van Doorn (Sep 30 2022 at 15:15):

#print pascal._main._pack.equations._eqn_1 gives you the answer (in a way...)

Kyle Miller (Sep 30 2022 at 15:23):

I wonder if we should have a refl-like tactic that operates by unfold-ing definitions? Like simp [pascal] but where you don't need to write pascal. The simp! tactic is not quite right since it uses delta unfolding.

The reason I suggest this is that rfl not being able to compute when there is well-founded recursion is a recurring question.

Another option is some sort of reduce tactic that uses all the equation lemmas. I'm imagining we could have a dec_trivial variant that uses it.

Mario Carneiro (Sep 30 2022 at 16:39):

simp! uses the equation lemmas IIUC

Kyle Miller (Sep 30 2022 at 16:55):

I checked, and simp! gives the has_well_founded.wf.fix ... term here


Last updated: Dec 20 2023 at 11:08 UTC