Structurally recursive functions are those in which each recursive call is on a structurally smaller term than the argument.
The same parameter must decrease in all recursive calls; this parameter is called the decreasing parameter.
Structural recursion is stronger than the primitive recursion that recursors provide, because the recursive call can use more deeply nested sub-terms of the argument, rather than only an immediate sub-term.
The constructions used to implement structural recursion are, however, implemented using the recursor; these helper constructions are described in the section on inductive types.
The rules that govern structural recursion are fundamentally syntactic in nature.
There are many recursive definitions that exhibit structurally recursive computational behavior, but which are not accepted by these rules; this is a fundamental consequence of the analysis being fully automatic.
Well-founded recursion provides a semantic approach to demonstrating termination that can be used in situations where a recursive function is not structurally recursive, but it can also be used when a function that computes according to structural recursion doesn't satisfy the syntactic requirements.
Structural Recursion vs Subtraction
The function countdown is structurally recursive.
The parameter n was matched against the pattern n'+1, which means that n' is a direct subterm of n in the second branch of the pattern match:
Replacing pattern matching with an equivalent Boolean test and subtraction results in an error:
deffail to show termination for
countdown'
with errors
failed to infer structural recursion:
Cannot use parameter n:
failed to eliminate recursive application
countdown' n'
failed to prove termination, possible solutions:
- Use `have`-expressions to prove the remaining goals
- Use `termination_by` to specify a different well-founded relation
- Use `decreasing_by` to specify your own tactic for discharging this kind of goal
n : Nat
hβ : Β¬(n == 0) = true
n' : Nat := n - 1
β’ n - 1 < ncountdown'(n:Nat):ListNat:=ifn==0then[]elseletn':=n-1n'::countdown'n'
fail to show termination for
countdown'
with errors
failed to infer structural recursion:
Cannot use parameter n:
failed to eliminate recursive application
countdown' n'
failed to prove termination, possible solutions:
- Use `have`-expressions to prove the remaining goals
- Use `termination_by` to specify a different well-founded relation
- Use `decreasing_by` to specify your own tactic for discharging this kind of goal
n : Nat
hβ : Β¬(n == 0) = true
n' : Nat := n - 1
β’ n - 1 < n
This is because there was no pattern matching on the parameter n.
While this function indeed terminates, the argument that it does so is based on properties of if, the equality test, and subtraction, rather than being a generic feature of Nat being an inductive type.
These arguments are expressed using well-founded recursion, and a slight change to the function definition allows Lean's automatic support for well-founded recursion to construct an alternative termination proof.
This version branches on the decidability of propositional equality for Nat rather than the result of a Boolean equality test:
Here, Lean's automation automatically constructs a termination proof from facts about propositional equality and subtraction.
It uses well-founded recursion rather than structure recursion behind the scenes.
Structural recursion may be used explicitly or automatically.
With explicit structural recursion, the function definition declares which parameter is the decreasing parameter.
If no termination strategy is explicitly declared, Lean performs a search for a decreasing parameter as well as a decreasing measure for use with well-founded recursion.
Explicitly annotating structural recursion has the following benefits:
It can speed up elaboration, because no search occurs.
It documents the termination argument for readers.
In situations where structural recursion is explicitly desired, it prevents the accidental use of well-founded recursion.
5.2.1.Β Explicit Structural Recursion
To explicitly use structural recursion, a function or theorem definition can be annotated with a Lean.Parser.Command.declaration : commandtermination_by structural clause that specifies the decreasing parameter.
The decreasing parameter may be a reference to a parameter named in the signature.
When the signature specifies a function type, the decreasing parameter may additionally be a parameter not named in the signature; in this case, names for the remaining parameters may be introduced by writing them before an arrow (Lean.Parser.Command.declaration : command=>).
Specifying Decreasing Parameters
When the decreasing parameter is a named parameter to the function, it can be specified by referring to its name.
When the decreasing parameter is not named in the signature, a name can be introduced locally in the Lean.Parser.Command.declaration : commandtermination_by clause.
The termination_by structural clause introduces a decreasing parameter.
Specify a termination argument for recursive functions.
```
termination_by a - b
```
indicates that termination of the currently defined recursive function follows
because the difference between the arguments `a` and `b` decreases.
If the function takes further argument after the colon, you can name them as follows:
```
def example (a : Nat) : Nat β Nat β Nat :=
termination_by b c => a - b
```
By default, a `termination_by` clause will cause the function to be constructed using well-founded
recursion. The syntax `termination_by structural a` (or `termination_by structural _ c => c`)
indicates the function is expected to be structural recursive on the argument. In this case
the body of the `termination_by` clause must be one of the function's parameters.
If omitted, a termination argument will be inferred. If written as `termination_by?`,
the inferrred termination argument will be suggested.
terminationBy::= ...
|Specify a termination argument for recursive functions.
```
termination_by a - b
```
indicates that termination of the currently defined recursive function follows
because the difference between the arguments `a` and `b` decreases.
If the function takes further argument after the colon, you can name them as follows:
```
def example (a : Nat) : Nat β Nat β Nat :=
termination_by b c => a - b
```
By default, a `termination_by` clause will cause the function to be constructed using well-founded
recursion. The syntax `termination_by structural a` (or `termination_by structural _ c => c`)
indicates the function is expected to be structural recursive on the argument. In this case
the body of the `termination_by` clause must be one of the function's parameters.
If omitted, a termination argument will be inferred. If written as `termination_by?`,
the inferrred termination argument will be suggested.
termination_bystructural(ident*=>)?term
The identifiers before the optional => can bring function parameters into scope that are not
already bound in the declaration header, and the mandatory term must indicate one of the function's parameters, whether introduced in the header or locally in the clause.
The decreasing parameter must satisfy the following conditions:
If its type is an indexed family, then all indices must be parameters of the function.
If the inductive or indexed family of the decreasing parameter has data type parameters, then these data type parameters may themselves only depend on function parameters that are part of the fixed prefix.
A fixed parameter is a function parameter that is passed unmodified in all recursive calls and is not an index of the recursive parameter's type.
The fixed prefix is the longest prefix of the function's parameters in which all are fixed.
Ineligible decreasing parameters
The decreasing parameter's type must be an inductive type.
In notInductive, a function is specified as the decreasing parameter:
defnotInductive(x:NatβNat):Nat:=notInductive(funn=>x(n+1))cannot use specified parameter for structural recursion:
its type is not an inductivetermination_bystructuralx
cannot use specified parameter for structural recursion:
its type is not an inductive
If the decreasing parameter is an indexed family, all the indices must be variables.
In constantIndex, the indexed family Fin' is instead applied to a constant value:
inductiveFin':NatβTypewhere|zero:Fin'(n+1)|succ:Fin'nβFin'(n+1)defconstantIndex(x:Fin'100):Nat:=constantIndex.zerocannot use specified parameter for structural recursion:
its type Fin' is an inductive family and indices are not variables
Fin' 100termination_bystructuralx
cannot use specified parameter for structural recursion:
its type Fin' is an inductive family and indices are not variables
Fin' 100
The parameters of the decreasing parameter's type must not depend on function parameters that come after varying parameters or indices.
In afterVarying, the fixed prefix is empty, because the first parameter n varies, so p is not part of the fixed prefix:
inductiveWithParam'(p:Nat):NatβTypewhere|zero:WithParam'p(n+1)|succ:WithParam'pnβWithParam'p(n+1)defafterVarying(n:Nat)(p:Nat)(x:WithParam'pn):Nat:=afterVarying(n+1)p.zerocannot use specified parameter for structural recursion:
its type is an inductive datatype
WithParam' p n
and the datatype parameter
p
depends on the function parameter
p
which does not come before the varying parameters and before the indices of the recursion parameter.termination_bystructuralx
cannot use specified parameter for structural recursion:
its type is an inductive datatype
WithParam' p n
and the datatype parameter
p
depends on the function parameter
p
which does not come before the varying parameters and before the indices of the recursion parameter.
Furthermore, every recursive call of the functions must be on a strict sub-term of the decreasing
parameter.
The decreasing parameter itself is a sub-term, but not a strict sub-term.
If a sub-term is the discriminant of a Lean.Parser.Term.match : termPattern matching. `match e, ... with | p, ... => f | ...` matches each given
term `e` against each pattern `p` of a match alternative. When all patterns
of an alternative match, the `match` term evaluates to the value of the
corresponding right-hand side `f` with the pattern variables bound to the
respective matched values.
If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available
within `f`.
When not constructing a proof, `match` does not automatically substitute variables
matched on in dependent variables' types. Use `match (generalizing := true) ...` to
enforce this.
Syntax quotations can also be used in a pattern match.
This matches a `Syntax` value against quotations, pattern variables, or `_`.
Quoted identifiers only match identical identifiers - custom matching such as by the preresolved
names only should be done explicitly.
`Syntax.atom`s are ignored during matching by default except when part of a built-in literal.
For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they
should participate in matching.
For example, in
```lean
syntax "c" ("foo" <|> "bar") ...
```
`foo` and `bar` are indistinguishable during matching, but in
```lean
syntax foo := "foo"
syntax "c" (foo <|> "bar") ...
```
they are not.
match expression or other pattern-matching syntax, the pattern that matches the discriminant is a sub-term in the right-hand side of each match alternative.
In particular, the rules of match generalization are used to connect the discriminant to the occurrences of the pattern term in the right-hand side; thus, it respects definitional equality.
The pattern is a strict sub-term if and only if the discriminant is a strict sub-term.
If a sub-term is a constructor applied to arguments, then its recursive arguments are strict sub-terms.
Nested Patterns and Sub-Terms
In the following example, the decreasing parameter n is matched against the nested pattern .succ(.succn). Therefore .succ(.succn) is a (non-strict) sub-term of n, and consequently both n and .succn are strict sub-terms, and the definition is accepted.
For clarity, this example uses .succn and .succ(.succn) instead of the equivalent Nat-specific n+1 and n+2.
Matching on Complex Expressions Can Prevent Elaboration
In the following example, the decreasing parameter n is not directly the discriminant of the Lean.Parser.Term.match : termPattern matching. `match e, ... with | p, ... => f | ...` matches each given
term `e` against each pattern `p` of a match alternative. When all patterns
of an alternative match, the `match` term evaluates to the value of the
corresponding right-hand side `f` with the pattern variables bound to the
respective matched values.
If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available
within `f`.
When not constructing a proof, `match` does not automatically substitute variables
matched on in dependent variables' types. Use `match (generalizing := true) ...` to
enforce this.
Syntax quotations can also be used in a pattern match.
This matches a `Syntax` value against quotations, pattern variables, or `_`.
Quoted identifiers only match identical identifiers - custom matching such as by the preresolved
names only should be done explicitly.
`Syntax.atom`s are ignored during matching by default except when part of a built-in literal.
For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they
should participate in matching.
For example, in
```lean
syntax "c" ("foo" <|> "bar") ...
```
`foo` and `bar` are indistinguishable during matching, but in
```lean
syntax foo := "foo"
syntax "c" (foo <|> "bar") ...
```
they are not.
match expression.
Therefore, n' is not considered a sub-term of n.
failed to infer structural recursion:
Cannot use parameter n:
failed to eliminate recursive application
half n'
defhalf(n:Nat):Nat:=matchOption.somenwith|.some(n'+2)=>halfn'+1|_=>0termination_bystructuraln
failed to infer structural recursion:
Cannot use parameter n:
failed to eliminate recursive application
half n'
Using well-founded recursion, and explicitly connecting the discriminant to the pattern of the match, this definition can be accepted.
Similarly, the following example fails: although xs.tail would reduce to a strict sub-term of xs, this is not visible to Lean according to the rules above.
In particular, xs.tail is not definitionally equal to a strict sub-term of xs.
failed to infer structural recursion:
Cannot use parameter #2:
failed to eliminate recursive application
listLen xs.tail
deflistLen:ListΞ±βNat|[]=>0|xs=>listLenxs.tail+1termination_bystructuralxs=>xsSimultaneous Matching vs Matching Pairs for Structural Recursion
An important consequence of the strategies that are used to prove termination is that simultaneous matching of two discriminants is not equivalent to matching a pair.
Simultaneous matching maintains the connection between the discriminants and the patterns, allowing the pattern matching to refine the types of the assumptions in the local context as well as the expected type of the Lean.Parser.Term.match : termPattern matching. `match e, ... with | p, ... => f | ...` matches each given
term `e` against each pattern `p` of a match alternative. When all patterns
of an alternative match, the `match` term evaluates to the value of the
corresponding right-hand side `f` with the pattern variables bound to the
respective matched values.
If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available
within `f`.
When not constructing a proof, `match` does not automatically substitute variables
matched on in dependent variables' types. Use `match (generalizing := true) ...` to
enforce this.
Syntax quotations can also be used in a pattern match.
This matches a `Syntax` value against quotations, pattern variables, or `_`.
Quoted identifiers only match identical identifiers - custom matching such as by the preresolved
names only should be done explicitly.
`Syntax.atom`s are ignored during matching by default except when part of a built-in literal.
For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they
should participate in matching.
For example, in
```lean
syntax "c" ("foo" <|> "bar") ...
```
`foo` and `bar` are indistinguishable during matching, but in
```lean
syntax foo := "foo"
syntax "c" (foo <|> "bar") ...
```
they are not.
match.
Essentially, the elaboration rules for Lean.Parser.Term.match : termPattern matching. `match e, ... with | p, ... => f | ...` matches each given
term `e` against each pattern `p` of a match alternative. When all patterns
of an alternative match, the `match` term evaluates to the value of the
corresponding right-hand side `f` with the pattern variables bound to the
respective matched values.
If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available
within `f`.
When not constructing a proof, `match` does not automatically substitute variables
matched on in dependent variables' types. Use `match (generalizing := true) ...` to
enforce this.
Syntax quotations can also be used in a pattern match.
This matches a `Syntax` value against quotations, pattern variables, or `_`.
Quoted identifiers only match identical identifiers - custom matching such as by the preresolved
names only should be done explicitly.
`Syntax.atom`s are ignored during matching by default except when part of a built-in literal.
For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they
should participate in matching.
For example, in
```lean
syntax "c" ("foo" <|> "bar") ...
```
`foo` and `bar` are indistinguishable during matching, but in
```lean
syntax foo := "foo"
syntax "c" (foo <|> "bar") ...
```
they are not.
match treat the discriminants specially, and changing discriminants in a way that preserves the run-time meaning of a program does not necessarily preserve the compile-time meaning.
This function that finds the minimum of two natural numbers is defined by structural recursion on its first parameter:
Replacing the simultaneous pattern match on both parameters with a match on a pair causes termination analysis to fail:
failed to infer structural recursion:
Cannot use parameter n:
failed to eliminate recursive application
min' n' k'
defmin'(nk:Nat):Nat:=match(n,k)with|(0,_)=>0|(_,0)=>0|(n'+1,k'+1)=>min'n'k'+1termination_bystructuraln
failed to infer structural recursion:
Cannot use parameter n:
failed to eliminate recursive application
min' n' k'
This is because the analysis only considers direct pattern matching on parameters when matching recursive calls to strictly-smaller argument values.
Wrapping the discriminants in a pair breaks the connection.
Structural Recursion Under Pairs
This function that finds the minimum of the two components of a pair can't be elaborated via structural recursion.
failed to infer structural recursion:
Cannot use parameter nk:
the type Nat Γ Nat does not have a `.brecOn` recursor
defmin'(nk:NatΓNat):Nat:=matchnkwith|(0,_)=>0|(_,0)=>0|(n'+1,k'+1)=>min'(n',k')+1termination_bystructuralnk
failed to infer structural recursion:
Cannot use parameter nk:
the type Nat Γ Nat does not have a `.brecOn` recursor
This is because the parameter's type, Prod, is not recursive.
Thus, its constructor has no recursive parameters that can be exposed by pattern matching.
def'min'' has already been declaredmin'(nk:NatΓNat):Nat:=matchnkwith|(0,_)=>0|(_,0)=>0|(n'+1,k'+1)=>min'(n',k')+1termination_bynkStructural Recursion and Definitional Equality
Even though the recursive occurrence of countdown is applied to a term that is not a strict sub-term of the decreasing parameter, the following definition is accepted:
In countdown', the recursive occurrence is applied to 0+n', which is not definitionally equal to n' because addition on natural numbers is structurally recursive in its second parameter:
failed to infer structural recursion:
Cannot use parameter n:
failed to eliminate recursive application
countdown' (0 + n')
defcountdown'(n:Nat):ListNat:=matchnwith|0=>[]|n'+1=>n'::countdown'(0+n')termination_bystructuraln
failed to infer structural recursion:
Cannot use parameter n:
failed to eliminate recursive application
countdown' (0 + n')
Lean supports the definition of mutually recursive functions using structural recursion.
Mutual recursion may be introduced using a mutual block, but it also results from Lean.Parser.Term.letrec : termlet rec expressions and Lean.Parser.Command.declaration : commandwhere blocks.
The rules for mutual structural recursion are applied to a group of actually mutually recursive, lifted definitions, that results from the elaboration steps for mutual groups.
If every function in the mutual group has a termination_by structural annotation indicating that functionβs decreasing argument, then structural recursion is used to translate the definitions.
The requirements on the decreasing argument above are extended:
All the types of all the decreasing arguments must be from the same inductive type, or more generally from the same mutual group of inductive types.
The parameters of the decreasing parameter's types must be the same for all functions, and may depend only on the common fixed prefix of function arguments.
The functions do not have to be in a one-to-one correspondence to the mutual inductive types.
Multiple functions can have a decreasing argument of the same type, and not all types that are mutually recursive with the decreasing argument need have a corresponding function.
Mutual Structural Recursion Over Non-Mutual Types
The following example demonstrates mutual recursion over a non-mutual inductive data type:
mutualdefeven:NatβProp|0=>True|n+1=>oddntermination_bystructuraln=>ndefodd:NatβProp|0=>False|n+1=>evenntermination_bystructuraln=>nendMutual Structural Recursion Over Mutual Types
The following example demonstrates recursion over mutually inductive types.
The functions Exp.size and App.size are mutually recursive.
The definition of App.numArgs is structurally recursive over type App.
It demonstrates that not all inductive types in the mutual group need to be handled.
If no termination_by clauses are present in a recursive or mutually recursive function definition, then Lean attempts to infer a suitable structurally decreasing argument, effectively by trying all suitable parameters in sequence.
If this search fails, Lean then attempts to infer well-founded recursion.
For mutually recursive functions, all combinations of parameters are tried, up to a limit to avoid combinatorial explosion.
If only some of the mutually recursive functions have termination_by structural clauses, then only those parameters are considered, while for the other functions all parameters are considered for structural recursion.
A termination_by? clause causes the inferred termination annotation to be shown.
It can be automatically added to the source file using the offered suggestion or code action.
Inferred Termination Annotations
Lean automatically infers that the function half is structurally recursive.
The termination_by? clause causes the inferred termination annotation to be displayed, and it can be automatically added to the source file with a single click.
defhalf:NatβNat|0|1=>0|n+2=>halfn+1Try this: termination_by structural x => xtermination_by?
Try this: termination_by structural x => x
5.2.4.Β Elaboration Using Course-of-Values Recursion
In this section, the construction used to elaborate structurally recursive functions is explained in more detail.
This elaboration uses the below and brecOn constructions that are automatically generated from inductive types' recursors.
Recursion vs Recursors
Addition of natural numbers can be defined via recursion on the second argument.
This function is straightforwardly structurally recursive.
Structural recursive calls made on data that isn't the immediate child of the function parameter requires either creativity or a complex yet systematic encoding.
Instead of creativity, a general technique called course-of-values recursion can be used.
Course-of-values recursion uses helpers that can be systematically derived for every inductive type, defined in terms of the recursor; Lean derives them automatically.
For every Natn, the type n.below(motive:=mot) provides a value of type motk for all k<n, represented as an iterated dependent pair type.
The course-of-values recursor Nat.brecOn allows a function to use the result for any smaller Nat.
Using it to define the function is inconvenient:
The function is marked Lean.Parser.Command.declaration : commandnoncomputable because the compiler doesn't support generating code for course-of-values recursion, which is intended for reasoning rather that efficient code.
The kernel can still be used to test the function, however:
However, it is now far from the original definition and it has become difficult for most people to understand.
Recursors are an excellent logical foundation, but not an easy way to write programs or proofs.
The structural recursion analysis attempts to translate the recursive pre-definition into a use of the appropriate structural recursion constructions.
At this step, pattern matching has already been translated into the use of matcher functions; these are treated specially by the termination checker.
Next, for each group of parameters, a translation using brecOn is attempted.
For both lists and trees, the brecOn operator expects just a single case, rather than one per constructor.
This case accepts a list or tree along with a table of results for all smaller values; from this, it should satisfy the motive for the provided value.
Dependent case analysis of the provided value automatically refines the type of the memo table, providing everything needed.
The following definitions are equivalent to List.brecOn and Tree.brecOn, respectively.
The primitive recursive helpers List.brecOnTable and Tree.brecOnTable compute the course-of-values tables along with the final results, and the actual definitions of the brecOn operators simply project out the result.
The below construction is a mapping from each value of a type to the results of some function call on all smaller values; it can be understood as a memoization table that already contains the results for all smaller values.
The notion of βsmaller valueβ that is expressed in the below construction corresponds directly to the definition of strict sub-terms.
Recursors expect an argument for each of the inductive type's constructors; these arguments are called with the constructor's arguments (and the result of recursion on recursive parameters) during ΞΉ-reduction.
The course-of-values recursion operator brecOn, on the other hand, expects just a single case that covers all constructors at once.
This case is provided with a value and a below table that contains the results of recursion on all values smaller than the given value; it should use the contents of the table to satisfy the motive for the provided value.
If the function is structurally recursive over a given parameter (or parameter group), then the results of all recursive calls will be present in this table already.
When the body of the recursive function is transformed into an invocation of brecOn on one of the function's parameters, the parameter and its course-of-values table are in scope.
The analysis traverses the body of the function, looking for recursive calls.
If the parameter is matched against, then its occurrences in the local context are generalized and then instantiated with the pattern; this is also true for the type of the course-of-values table.
Typically, this pattern matching results in the type of the course-of-values table becoming more specific, which gives access to the recursive results for smaller values.
This generalization process implements the rule that patterns are sub-terms of match discriminants.
When an recursive occurrence of the function is detected, the course-of-values table is consulted to see whether it contains a result for the argument being checked.
If so, the recursive call can be replaced with a projection from the table.
If not, then the parameter in question doesn't support structural recursion.
Elaboration Walkthrough
The first step in walking through the elaboration of half is to manually desugar it to a simpler form.
This doesn't match the way Lean works, but its output is much easier to read when there are fewer OfNat instances present.
This readable definition:
The elaborator begins by elaborating a pre-definition in which recursion is still present but the definition is otherwise in Lean's core type theory.
Turning on the compiler's tracing of pre-definitions, as well as making the pretty printer more explicit, makes the resulting pre-definition visible:
In other words, the specific configuration of patterns used in half are captured in half.match_1.
This definition is a more readable version of half's pre-definition:
defhalf':NatβNat:=fun(x:Nat)=>half.match_1(motive:=fun_=>Nat)x(fun_=>0) -- Case for 0
(fun_=>0) -- Case for 1
(funn=>Nat.succ(half'n)) -- Case for n + 2
To elaborate it as a structurally recursive function, the first step is to establish the bRecOn invocation.
The definition must be marked Lean.Parser.Command.declaration : commandnoncomputable because Lean does not support code generation for recursors such as Nat.brecOn.
noncomputabledefhalf'':NatβNat:=fun(x:Nat)=>x.brecOnfunntable=>don't know how to synthesize placeholder
context:
x n : Nat
table : Nat.below n
β’ Nat_
/- To translate:
half.match_1 (motive := fun _ => Nat) x
(fun _ => 0) -- Case for 0
(fun _ => 0) -- Case for 1
(fun n => Nat.succ (half' n)) -- Case for n + 2
-/
The next step is to replace occurrences of x in the original function body with the n provided by brecOn.
Because table's type depends on x, it must also be generalized when splitting cases with half.match_1, leading to a motive with an extra parameter.
noncomputabledefhalf'':NatβNat:=fun(x:Nat)=>x.brecOnfunntable=>(half.match_1(motive:=funk=>k.below(motive:=fun_=>Nat)βNat)ndon't know how to synthesize placeholder for argument 'h_1'
context:
x n : Nat
table : Nat.below n
β’ Unit β (fun k => Nat.below k β Nat) Nat.zero_don't know how to synthesize placeholder for argument 'h_2'
context:
x n : Nat
table : Nat.below n
β’ Unit β (fun k => Nat.below k β Nat) 1_don't know how to synthesize placeholder for argument 'h_3'
context:
x n : Nat
table : Nat.below n
β’ (n : Nat) β (fun k => Nat.below k β Nat) n.succ.succ_)table
/- To translate:
(fun _ => 0) -- Case for 0
(fun _ => 0) -- Case for 1
(fun n => Nat.succ (half' n)) -- Case for n + 2
-/
The three cases' placeholders expect the following types:
don't know how to synthesize placeholder for argument 'h_1'
context:
x n : Nat
table : Nat.below n
β’ Unit β (fun k => Nat.below k β Nat) Nat.zero
don't know how to synthesize placeholder for argument 'h_2'
context:
x n : Nat
table : Nat.below n
β’ Unit β (fun k => Nat.below k β Nat) 1
don't know how to synthesize placeholder for argument 'h_3'
context:
x n : Nat
table : Nat.below n
β’ (n : Nat) β (fun k => Nat.below k β Nat) n.succ.succ
The first two cases in the pre-definition are constant functions, with no recursion to check:
noncomputabledefhalf'':NatβNat:=fun(x:Nat)=>x.brecOnfunntable=>(half.match_1(motive:=funk=>k.below(motive:=fun_=>Nat)βNat)n(fun()_=>.zero)(fun()_=>.zero)don't know how to synthesize placeholder for argument 'h_3'
context:
x n : Nat
table : Nat.below n
β’ (n : Nat) β (fun k => Nat.below k β Nat) n.succ.succ_)table
/- To translate:
(fun n => Nat.succ (half' n)) -- Case for n + 2
-/
The final case contains a recursive call.
It should be translated into a lookup into the course-of-values table.
A more readable representation of the last hole's type is:
The first Nat in the course-of-values table is the result of recursion on n+1, and the second is the result of recursion on n.
The recursive call can thus be replaced by a lookup, and the elaboration is successful:
noncomputabledefhalf'':NatβNat:=fun(x:Nat)=>x.brecOnfunntable=>(half.match_1(motive:=funk=>k.below(motive:=fun_=>Nat)βNat)n(fun()_=>.zero)(fun()_=>.zero)(fun_table=>Nat.succtable.2.1)tableunexpected end of input; expected ')', ',' or ':'
The actual elaborator keeps track of the relationship between the parameter being checked for structural recursion and the positions in the course-of-values tables by inserting sentinel types with fresh names into the motive.
Planned Content
A description of the elaboration of mutually recursive functions