lean4-htt/tests/lean/run/terminationByStructurally.lean
Joachim Breitner 1311e36a98
feat: structural mutual recursion (#4575)
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>
2024-07-08 14:39:50 +00:00

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