Zulip Chat Archive
Stream: lean4
Topic: How is Nat.below and Nat.brecOn compiled
Joachim Breitner (Oct 22 2023 at 11:44):
When I elaborate a function using structural recursion, like in
def foo : Nat → Bool
| 0 => true
| n+1 => foo n
#print foo
I see it’s using Nat.brecOn
to compile it:
def foo : Nat → Bool :=
fun x =>
Nat.brecOn x fun x f =>
(match (motive := (x : Nat) → Nat.below x → Bool) x with
| 0 => fun x => true
| Nat.succ n => fun x => x.fst.fst)
f
The argument of Nat.brecOn
here is (n : Nat) → Nat.below n → Bool
, and @Nat.below motive n
is a dependent vector of size n
, (inductively defined and isomorphic to (i : Fin n) → motive i
).
This looks like it would perform memoization, if the Nat.below n
argument contains all the various for i < n
, but it doesn't seem to be compiled that way.
So I wonder: How does the compiler turn the x.fst.fst
into a normal recursive function calls? And why go through this detour in the first place?
(RTFM with pointers are welcome too :-))
Joachim Breitner (Oct 22 2023 at 12:34):
I may be able to answer the “why”: This way function definitions that are not just primitively recursive can be expressed using this kind of structural recursion, e.g. the typical fib
function
def fib : Nat → Bool
| 0 => true
| 1 => true
| n+2 => fib n && fib (n + 1)
#print fib
doesn't need to resort to well-founded recursion.
Joachim Breitner (Oct 22 2023 at 12:39):
Ok, and the answer of “how is it compiled” might simply be “not at all”. If I manually write a function using Nat.brecOn
in a way that I might try to tease the code generator with, like
def sum_below : (n : Nat) → @Nat.below (fun _ => Nat) n → Nat
| 0, .unit => 0
| n+1, ⟨⟨i, b⟩, .unit⟩ => i + sum_below n b
def foo (n : Nat) : Nat :=
Nat.brecOn (motive := fun _ => Nat) 0 sum_below
which would look at _all_ the previous values in the Nat.below
, it says
code generator does not support recursor 'Nat.brecOn' yet, consider using 'match ... with' and/or structural recursion
So I conclude that the input to the code generator is _not_ the form I see with #print
, with recursion replaced with Nat.brecOn
, but some earlier, not-yet-elaborated-to-recursers-form of the function definition? Is that correct?
Sebastian Ullrich (Oct 22 2023 at 14:36):
Yes, that is exactly right. And the potentially unexpected memoization from brecOn
that would otherwise happen is one of the and perhaps the main reason for that.
Joachim Breitner (Oct 22 2023 at 15:00):
Thanks! What is the “earlier, not-yet-elaborated” form that is then compiled?
Sebastian Ullrich (Oct 22 2023 at 15:03):
Just a direct recursive call, which the compiler understands in contrast to the kernel
Joachim Breitner (Oct 22 2023 at 18:51):
Browsing the code a bit it seems that “earlier” state, where recursion is still explicit, is a PreDefinition
and addPreDefinitions
in src/Lean/Elab/PreDefinition/Main.lean
tries various strategies to turn this into a Declaration
with recursers which can be added to the environment.
For example in def structuralRecursion
then it produces a preDefNonRec
which is added to the kernel’s environment with addNonRec
, and it also produces a let preDef ← eraseRecAppSyntax preDefs[0]!
with arbitrary recursion, which is compiled using addAndCompilePartialRec #[preDef]
.
Oddly, addAndCompilePartialRec
via addAndCompileUnsafe
calls eraseRecAppSyntax
, but why not, doppelt hält besser.
addAndCompileUnsafe
creates a Declaration.mutualDefnDecl
, which I understand can be compiled. I am a bit surprised that this declaration (which the kernel can’t handle, can it) is still passed to addDecl decl
…
Ok, that goes into C++ land, and there it checks that mutual definitions are always partial
or unsafe
… makes sense.
But why is there no name clash now between the recurser-based non-recursive definition, and the mutually recursive one, when both are added to the environment? Does one of them get a mangled name?
Sebastian Ullrich (Oct 22 2023 at 20:12):
I believe mutualDefnDecl
is used basically by the compiler only, so there is no interaction or clash with defnDecl
s
Last updated: Dec 20 2023 at 11:08 UTC