This adds support for mutual structural recursive functions. For now this is opt-in: The functions must have a `termination_by structural …` annotation (new since #4542) for this to work: ```lean mutual inductive A | self : A → A | other : B → A | empty inductive B | self : B → B | other : A → B | empty end mutual def A.size : A → Nat | .self a => a.size + 1 | .other b => b.size + 1 | .empty => 0 termination_by structural x => x def B.size : B → Nat | .self b => b.size + 1 | .other a => a.size + 1 | .empty => 0 termination_by structural x => x end ``` The recursive functions don’t have to be in a one-to-one relation to a set of mutually recursive inductive data types. It is possible to ignore some of the types: ```lean def A.self_size : A → Nat | .self a => a.self_size + 1 | .other _ => 0 | .empty => 0 termination_by structural x => x ``` or have more than one function per argument type: ```lean def isEven : Nat → Prop | 0 => True | n+1 => ¬ isOdd n termination_by structural x => x def isOdd : Nat → Prop | 0 => False | n+1 => ¬ isEven n termination_by structural x => x ``` This does not include * Support for nested inductive data types or nested recursion * Inferring mutual structural recursion in the absence of `termination_by`. * Functional induction principles for these. * Mutually recursive functions that live in different universes. This may be possible, maybe after beefing up the `.below` and `.brecOn` functions; we can look into this some other time, maybe when there are concrete use cases. --------- Co-authored-by: Richard Kiss <him@richardkiss.com> Co-authored-by: Tobias Grosser <tobias@grosser.es>
79 lines
1.9 KiB
Text
79 lines
1.9 KiB
Text
|
|
def foo (n : Nat) : Nat := match n with
|
|
| 0 => 0
|
|
| n+1 => foo n
|
|
termination_by structural n
|
|
|
|
-- Test that this is indeed by structural recursion
|
|
example : foo (n + 3) = foo n := Eq.refl _
|
|
|
|
|
|
-- Check that we can still refer to a variable called `structural` in
|
|
-- the `termination_by` syntax
|
|
def bar (structural : Nat) : True := match structural with
|
|
| 0 => .intro
|
|
| structural+1 => bar structural
|
|
termination_by «structural»
|
|
|
|
namespace Errors
|
|
-- A few error conditions
|
|
|
|
/-- error: it is unchanged in the recursive calls -/
|
|
#guard_msgs in
|
|
def foo1 (n : Nat) : Nat := foo1 n
|
|
termination_by structural n
|
|
|
|
/--
|
|
error: its type Nat.le is an inductive family and indices are not variables
|
|
n.succ.le 100
|
|
-/
|
|
#guard_msgs in
|
|
def foo2 (n : Nat) (h : n < 100) : Nat := match n with
|
|
| 0 => 0
|
|
| n+1 => foo2 n (by omega)
|
|
termination_by structural h
|
|
|
|
/--
|
|
error: one parameter bound in `termination_by`, but the body of Errors.foo3 only binds 0 parameters.
|
|
-/
|
|
#guard_msgs in
|
|
def foo3 (n : Nat) : Nat → Nat := match n with
|
|
| 0 => id
|
|
| n+1 => foo3 n
|
|
termination_by structural m => m
|
|
|
|
/--
|
|
error: failed to eliminate recursive application
|
|
ackermann (n + 1) m
|
|
-/
|
|
#guard_msgs in
|
|
def ackermann (n m : Nat) := match n, m with
|
|
| 0, m => m + 1
|
|
| .succ n, 0 => ackermann n 1
|
|
| .succ n, .succ m => ackermann n (ackermann (n + 1) m)
|
|
termination_by structural n
|
|
|
|
/--
|
|
error: failed to eliminate recursive application
|
|
ackermann2 n 1
|
|
-/
|
|
#guard_msgs in
|
|
def ackermann2 (n m : Nat) := match n, m with
|
|
| 0, m => m + 1
|
|
| .succ n, 0 => ackermann2 n 1
|
|
| .succ n, .succ m => ackermann2 n (ackermann2 (n + 1) m)
|
|
termination_by structural m
|
|
|
|
/--
|
|
error: The termination argument of a structurally recursive function must be one of the parameters 'n', but
|
|
id n + 1
|
|
isn't one of these.
|
|
-/
|
|
#guard_msgs in
|
|
def foo4 (n : Nat) : Nat → Nat := match n with
|
|
| 0 => id
|
|
| n+1 => foo4 n
|
|
termination_by structural id n + 1
|
|
|
|
|
|
end Errors
|