Zulip Chat Archive

Stream: lean4

Topic: Simulating well founded recursions?


Ryan Lahfa (Apr 30 2021 at 08:43):

I'm trying to formalize the function compile.inv :

inductive LambdaTerm where
| var (val : Nat)
| app (fn: LambdaTerm) (arg: LambdaTerm)
| lambda (body: LambdaTerm)

inductive KrivineInstruction
| Access (n: Nat)
| Grab
| Push (c: List KrivineInstruction)

inductive KrivineEnv
| Item (instrs: List KrivineInstruction) (env: List KrivineEnv)

inductive KrivineStack
| Item (instrs: List KrivineInstruction) (env: List KrivineEnv)

structure KrivineState where
  code: List KrivineInstruction
  env: List KrivineEnv
  stack: List KrivineStack

def compile.inv: List KrivineInstruction -> LambdaTerm
| [] => LambdaTerm.var 0
| KrivineInstruction.Access n :: _ => LambdaTerm.var n
| KrivineInstruction.Push c' :: c => LambdaTerm.app (inv c) (inv c')
| KrivineInstruction.Grab :: c => LambdaTerm.lambda (inv c)

But it looks like Lean 4 is not able to show termination on compile.inv (due to https://github.com/leanprover/lean4/issues/176 I suppose), is there a way forward or tricks to still build this function manually and show stuff on it?

Daniel Fabian (Apr 30 2021 at 08:48):

does making it partial work for your case?

Ryan Lahfa (Apr 30 2021 at 08:51):

Daniel Fabian said:

does making it partial work for your case?

I'm getting

failed to compile partial definition 'compile.inv', failed to show that type is inhabited

Daniel Fabian (Apr 30 2021 at 08:51):

ah, you'll need to add a deriving Inhabited to your type.

Daniel Fabian (Apr 30 2021 at 08:51):

Probably to LambdaTerm

Sebastian Ullrich (Apr 30 2021 at 08:52):

You will not be able to prove anything about it then though

Ryan Lahfa (Apr 30 2021 at 08:53):

Ha, so indeed, deriving Inhabitedfor LambdaTerm and making it partial creates the function, but I still need to prove things on its composition with other functions

Ryan Lahfa (Apr 30 2021 at 08:55):

Okay, I see, partial makes the function unsafe and uses implementedBy, makes sense why I cannot prove anything on it

Sebastian Ullrich (Apr 30 2021 at 09:03):

You could try to apply Wellfounded.fixF manually or, probably nicer, define a custom "accessibility" inductive predicate for your datatype and do induction on that one. Not completely sure about that one though, I haven't tried yet.

Ryan Lahfa (Apr 30 2021 at 09:06):

Sebastian Ullrich said:

You could try to apply Wellfounded.fixF manually or, probably nicer, define a custom "accessibility" inductive predicate for your datatype and do induction on that one. Not completely sure about that one though, I haven't tried yet.

Alright, will give it a try, thanks!

Sebastian Ullrich (Apr 30 2021 at 09:11):

Well, I guess the predicate merely moves the issue since you still have to show by well-founded recursion that it is inhabited :) . So it might only make sense if you have multiple functions using the same recursion scheme. In other words, it is more or less equivalent to hiding the wf recursion behind a custom recursor.

Ryan Lahfa (Apr 30 2021 at 09:18):

Sebastian Ullrich said:

Well, I guess the predicate merely moves the issue since you still have to show by well-founded recursion that it is inhabited :) . So it might only make sense if you have multiple functions using the same recursion scheme. In other words, it is more or less equivalent to hiding the wf recursion behind a custom recursor.

This custom recursor can be used then with induction <> using custom_recursor syntax?

Ryan Lahfa (Apr 30 2021 at 09:40):

So I tried a real quick definition and got to this:

inductive LambdaTerm where
| var (val : Nat)
| app (fn: LambdaTerm) (arg: LambdaTerm)
| lambda (body: LambdaTerm)

inductive KrivineInstruction
| Access (n: Nat)
| Grab
| Push (c: List KrivineInstruction)

inductive KrivineEnv
| Item (instrs: List KrivineInstruction) (env: List KrivineEnv)

inductive KrivineStack
| Item (instrs: List KrivineInstruction) (env: List KrivineEnv)

structure KrivineState where
  code: List KrivineInstruction
  env: List KrivineEnv
  stack: List KrivineStack

def compile.inv_rel: List KrivineInstruction -> List KrivineInstruction -> Prop := sorry
def compile.inv_wf (x: List KrivineInstruction): Acc inv_rel x := sorry

def compile.inv (c: List KrivineInstruction): LambdaTerm :=
WellFounded.fixF (fun code inv => match code with
  | [] => LambdaTerm.var 0
  | KrivineInstruction.Access n :: _ => LambdaTerm.var n
  | KrivineInstruction.Push c' :: c => LambdaTerm.app (inv c (sorry)) (inv c' (sorry))
  | KrivineInstruction.Grab :: c => LambdaTerm.lambda (inv c (sorry))
) c (inv_wf c)

Now, Lean is giving me: (kernel) compiler failed to infer low level type, unknown declaration 'WellFounded.fixF' ; is this expected because I abused of sorry :> ?

Sebastian Ullrich (Apr 30 2021 at 09:47):

Oops, the compiler does not know how to handle wf recursion. You can turn it off using set_option codegen false. Or, if you want to run this def, do what the elaborator will do when it gains support for wf recursion and define a partial def for the compiler and an @[implementedBy ...] def for proving.

Ryan Lahfa (Apr 30 2021 at 09:51):

Sebastian Ullrich said:

Oops, the compiler does not know how to handle wf recursion. You can turn it off using set_option codegen false. Or, if you want to run this def, do what the elaborator will do when it gains support for wf recursion and define a partial def for the compiler and an @[implementedBy ...] def for proving.

so I would need to do something like:

partial def compile.inv 

@[implementedBy compile.inv]
def compile.inv_defined_with_wf 

?

Ryan Lahfa (Apr 30 2021 at 09:51):

oh no, I understood I think now

Ryan Lahfa (Apr 30 2021 at 09:53):

I tried:

partial def compile.inv: List KrivineInstruction -> LambdaTerm
| [] => LambdaTerm.var 0
| KrivineInstruction.Access n :: _ => LambdaTerm.var n
| KrivineInstruction.Push c' :: c => LambdaTerm.app (inv c) (inv c')
| KrivineInstruction.Grab :: c => LambdaTerm.lambda (inv c)

@[implementedBy compile.inv]
def compile.inv_with_wf (c: List KrivineInstruction): LambdaTerm :=
WellFounded.fixF (fun code inv => match code with
  | [] => LambdaTerm.var 0
  | KrivineInstruction.Access n :: _ => LambdaTerm.var n
  | KrivineInstruction.Push c' :: c => LambdaTerm.app (inv c (sorry)) (inv c' (sorry))
  | KrivineInstruction.Grab :: c => LambdaTerm.lambda (inv c (sorry))
) c (inv_wf c)

without much more luck, do I still need to disable codegen for the wf definition?

Sebastian Ullrich (Apr 30 2021 at 09:55):

You might, that could be an oversight in implementedBy

Ryan Lahfa (Apr 30 2021 at 09:57):

Alright, it did the trick I think, thanks!

Sebastian Ullrich (Apr 30 2021 at 09:58):

Nice!

Marcus Rossel (Aug 18 2021 at 12:35):

So the whole partial + implementedBy dance is only there to achieve efficient computation, right? That is, the partial def is not necessary if you're only interested in proving things about the definition?

Sebastian Ullrich (Aug 18 2021 at 12:38):

Yes, see
Sebastian Ullrich said:

You can turn it off using set_option codegen false. Or, if you want to run this def, do what the elaborator will do when it gains support for wf recursion and define a partial def for the compiler and an @[implementedBy ...] def for proving.


Last updated: Dec 20 2023 at 11:08 UTC