feat: drop support for termination_by' (#3033)

until around 7fe6881 the way to define well-founded recursions was to
specify a `WellFoundedRelation` on the argument explicitly. This was
rather low-level, for example one had to predict the packing of multiple
arguments into `PProd`s, the packing of mutual functions into `PSum`s,
and the cliques that were calculated.

Then the current `termination_by` syntax was introduced, where you
specify the termination argument at a higher level (one clause per
functions, unpacked arguments), and the `WellFoundedRelation` is found
using type class resolution.

The old syntax was kept around as `termination_by'`. This is not used
anywhere in the lean, std, mathlib or the theorem-proving-in-lean
repositories,
and three occurrences I found in the wild can do without

In particular, it should be possible to express anything that the old
syntax
supported also with the new one, possibly requiring a helper type with a
suitable instance, or the following generic wrapper that now lives in
std
```
def wrap {α : Sort u} {r : α → α → Prop} (h : WellFounded r) (x : α) : {x : α // Acc r x}
```

Since the old syntax is unused, has an unhelpful name and relies on
internals, this removes the support. Now is a good time before the
refactoring that's planned in #2921.

The test suite was updated without particular surprises.

The parametric `terminationHint` parser is gone, which means we can
match on syntax more easily now, in `expandDecreasingBy?`.
This commit is contained in:
Joachim Breitner 2023-12-11 18:33:17 +01:00 committed by GitHub
parent 178ab8ef2e
commit 5cd90f5826
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 253 additions and 353 deletions

View file

@ -20,6 +20,22 @@ v4.5.0 (development in progress)
```
[PR #2821](https://github.com/leanprover/lean4/pull/2821) and [RFC #2838](https://github.com/leanprover/lean4/issues/2838).
* The low-level `termination_by'` clause is no longer supported.
Migration guide: Use `termination_by` instead, e.g.:
```diff
-termination_by' measure (fun ⟨i, _⟩ => as.size - i)
+termination_by go i _ => as.size - i
```
If the well-founded relation you want to use is not the one that the
`WellFoundedRelation` type class would infer for your termination argument,
you can use `WellFounded.wrap` from the std libarary to explicitly give one:
```diff
-termination_by' ⟨r, hwf⟩
+termination_by _ x => hwf.wrap x
```
v4.4.0
---------

View file

@ -100,8 +100,8 @@ def addPreDefinitions (preDefs : Array PreDefinition) (hints : TerminationHints)
let preDefs ← preDefs.mapM ensureNoUnassignedMVarsAtPreDef
let preDefs ← betaReduceLetRecApps preDefs
let cliques := partitionPreDefs preDefs
let mut terminationBy ← liftMacroM <| WF.expandTerminationBy hints.terminationBy? (cliques.map fun ds => ds.map (·.declName))
let mut decreasingBy ← liftMacroM <| WF.expandTerminationHint hints.decreasingBy? (cliques.map fun ds => ds.map (·.declName))
let mut terminationBy ← liftMacroM <| WF.expandTerminationBy? hints.terminationBy? (cliques.map fun ds => ds.map (·.declName))
let mut decreasingBy ← liftMacroM <| WF.expandDecreasingBy? hints.decreasingBy? (cliques.map fun ds => ds.map (·.declName))
let mut hasErrors := false
for preDefs in cliques do
trace[Elab.definition.scc] "{preDefs.map (·.declName)}"

View file

@ -32,7 +32,6 @@ In addition to measures derived from `sizeOf xᵢ`, it also considers measures
that assign an order to the functions themselves. This way we can support mutual
function definitions where no arguments decrease from one function to another.
The result of this module is a `TerminationWF`, which is then passed on to `wfRecursion`; this
design is crucial so that whatever we infer in this module could also be written manually by the
user. It would be bad if there are function definitions that can only be processed with the
@ -590,7 +589,7 @@ def buildTermWF (declNames : Array Name) (varNamess : Array (Array Name))
declName, vars, body,
implicit := true
}
return .ext termByElements
return termByElements
/--

View file

@ -34,10 +34,10 @@ private partial def unpackUnary (preDef : PreDefinition) (prefixSize : Nat) (mva
let mut varNames ← xs.mapM fun x => x.fvarId!.getUserName
if element.vars.size > varNames.size then
throwErrorAt element.vars[varNames.size]! "too many variable names"
for i in [:element.vars.size] do
let varStx := element.vars[i]!
if varStx.isIdent then
varNames := varNames.set! (varNames.size - element.vars.size + i) varStx.getId
for h : i in [:element.vars.size] do
let varStx := element.vars[i]'h.2
if let `($ident:ident) := varStx then
varNames := varNames.set! (varNames.size - element.vars.size + i) ident.getId
return varNames
let mut mvarId := mvarId
for localDecl in (← Term.getMVarDecl mvarId).lctx, varName in varNames[:prefixSize] do
@ -60,25 +60,18 @@ def elabWFRel (preDefs : Array PreDefinition) (unaryPreDefName : Name) (fixedPre
let expectedType := mkApp (mkConst ``WellFoundedRelation [u]) α
trace[Elab.definition.wf] "elabWFRel start: {(← mkFreshTypeMVar).mvarId!}"
withDeclName unaryPreDefName do
match wf with
| TerminationWF.core wfStx =>
let wfRel ← instantiateMVars (← withSynthesize <| elabTermEnsuringType wfStx expectedType)
let pendingMVarIds ← getMVars wfRel
discard <| logUnassignedUsingErrorInfos pendingMVarIds
k wfRel
| TerminationWF.ext elements =>
withRef (getRefFromElems elements) do
let mainMVarId := (← mkFreshExprSyntheticOpaqueMVar expectedType).mvarId!
let [fMVarId, wfRelMVarId, _] ← mainMVarId.apply (← mkConstWithFreshMVarLevels ``invImage) | throwError "failed to apply 'invImage'"
let (d, fMVarId) ← fMVarId.intro1
let subgoals ← unpackMutual preDefs fMVarId d
for (d, mvarId) in subgoals, element in elements, preDef in preDefs do
let mvarId ← unpackUnary preDef fixedPrefixSize mvarId d element
mvarId.withContext do
let value ← Term.withSynthesize <| elabTermEnsuringType element.body (← mvarId.getType)
mvarId.assign value
let wfRelVal ← synthInstance (← inferType (mkMVar wfRelMVarId))
wfRelMVarId.assign wfRelVal
k (← instantiateMVars (mkMVar mainMVarId))
withRef (getRefFromElems wf) do
let mainMVarId := (← mkFreshExprSyntheticOpaqueMVar expectedType).mvarId!
let [fMVarId, wfRelMVarId, _] ← mainMVarId.apply (← mkConstWithFreshMVarLevels ``invImage) | throwError "failed to apply 'invImage'"
let (d, fMVarId) ← fMVarId.intro1
let subgoals ← unpackMutual preDefs fMVarId d
for (d, mvarId) in subgoals, element in wf, preDef in preDefs do
let mvarId ← unpackUnary preDef fixedPrefixSize mvarId d element
mvarId.withContext do
let value ← Term.withSynthesize <| elabTermEnsuringType element.body (← mvarId.getType)
mvarId.assign value
let wfRelVal ← synthInstance (← inferType (mkMVar wfRelMVarId))
wfRelMVarId.assign wfRelVal
k (← instantiateMVars (mkMVar mainMVarId))
end Lean.Elab.WF

View file

@ -5,44 +5,46 @@ Authors: Leonardo de Moura
-/
import Lean.Parser.Command
set_option autoImplicit false
namespace Lean.Elab.WF
/-! # Support for `decreasing_by` and `termination_by'` notations -/
/-! # Support for `decreasing_by` -/
structure TerminationHintValue where
structure DecreasingByTactic where
ref : Syntax
value : Syntax
value : Lean.TSyntax `Lean.Parser.Tactic.tacticSeq
deriving Inhabited
inductive TerminationHint where
inductive DecreasingBy where
| none
| one (val : TerminationHintValue)
| many (map : NameMap TerminationHintValue)
| one (val : DecreasingByTactic)
| many (map : NameMap DecreasingByTactic)
deriving Inhabited
open Parser.Command in
/--
This function takes a user-specified `decreasing_by` or `termination_by'` clause (as `Sytnax`).
If it is a `terminathionHintMany` (a set of clauses guarded by the function name) then
This function takes a user-specified `decreasing_by` clause (as `Sytnax`).
If it is a `decreasingByMany` (a set of clauses guarded by the function name) then
* checks that all mentioned names exist in the current declaration
* check that at most one function from each clique is mentioned
and sort the entries by function name.
-/
def expandTerminationHint (terminationHint? : Option Syntax) (cliques : Array (Array Name)) : MacroM TerminationHint := do
let some terminationHint := terminationHint? | return TerminationHint.none
let ref := terminationHint
match terminationHint with
| `(terminationByCore|termination_by' $hint1:terminationHint1)
| `(decreasingBy|decreasing_by $hint1:terminationHint1) =>
return TerminationHint.one { ref, value := hint1.raw[0] }
| `(terminationByCore|termination_by' $hints:terminationHintMany)
| `(decreasingBy|decreasing_by $hints:terminationHintMany) => do
let m ← hints.raw[0].getArgs.foldlM (init := {}) fun m arg =>
def expandDecreasingBy? (decreasingBy? : Option Syntax) (cliques : Array (Array Name)) : MacroM DecreasingBy := do
let some decreasingBy := decreasingBy? | return DecreasingBy.none
let ref := decreasingBy
match decreasingBy with
| `(decreasingBy|decreasing_by $hint1:tacticSeq) =>
return DecreasingBy.one { ref, value := hint1 }
| `(decreasingBy|decreasing_by $hints:decreasingByMany) => do
let m ← hints.raw[0].getArgs.foldlM (init := {}) fun m arg => do
let arg : TSyntax `decreasingByElement := ⟨arg⟩ -- cannot use syntax pattern match with lookahead
let `(decreasingByElement| $declId:ident => $tac:tacticSeq) := arg | Macro.throwUnsupported
let declName? := cliques.findSome? fun clique => clique.findSome? fun declName =>
if arg[0].getId.isSuffixOf declName then some declName else none
if declId.getId.isSuffixOf declName then some declName else none
match declName? with
| none => Macro.throwErrorAt arg[0] s!"function '{arg[0].getId}' not found in current declaration"
| some declName => return m.insert declName { ref := arg, value := arg[2] }
| none => Macro.throwErrorAt declId s!"function '{declId.getId}' not found in current declaration"
| some declName => return m.insert declName { ref := arg, value := tac }
for clique in cliques do
let mut found? := Option.none
for declName in clique do
@ -50,69 +52,65 @@ def expandTerminationHint (terminationHint? : Option Syntax) (cliques : Array (A
if let some found := found? then
Macro.throwErrorAt ref s!"invalid termination hint element, '{declName}' and '{found}' are in the same clique"
found? := some declName
return TerminationHint.many m
return DecreasingBy.many m
| _ => Macro.throwUnsupported
def TerminationHint.markAsUsed (t : TerminationHint) (clique : Array Name) : TerminationHint :=
def DecreasingBy.markAsUsed (t : DecreasingBy) (clique : Array Name) : DecreasingBy :=
match t with
| TerminationHint.none => TerminationHint.none
| TerminationHint.one .. => TerminationHint.none
| TerminationHint.many m => Id.run do
| DecreasingBy.none => DecreasingBy.none
| DecreasingBy.one .. => DecreasingBy.none
| DecreasingBy.many m => Id.run do
for declName in clique do
if m.contains declName then
let m := m.erase declName
if m.isEmpty then
return TerminationHint.none
return DecreasingBy.none
else
return TerminationHint.many m
return DecreasingBy.many m
return t
def TerminationHint.find? (t : TerminationHint) (clique : Array Name) : Option TerminationHintValue :=
def DecreasingBy.find? (t : DecreasingBy) (clique : Array Name) : Option DecreasingByTactic :=
match t with
| TerminationHint.none => Option.none
| TerminationHint.one v => some v
| TerminationHint.many m => clique.findSome? m.find?
| DecreasingBy.none => Option.none
| DecreasingBy.one v => some v
| DecreasingBy.many m => clique.findSome? m.find?
def TerminationHint.ensureAllUsed (t : TerminationHint) : MacroM Unit := do
def DecreasingBy.ensureAllUsed (t : DecreasingBy) : MacroM Unit := do
match t with
| TerminationHint.one v => Macro.throwErrorAt v.ref "unused termination hint element"
| TerminationHint.many m => m.forM fun _ v => Macro.throwErrorAt v.ref "unused termination hint element"
| DecreasingBy.one v => Macro.throwErrorAt v.ref "unused termination hint element"
| DecreasingBy.many m => m.forM fun _ v => Macro.throwErrorAt v.ref "unused termination hint element"
| _ => pure ()
/-! # Support for `termination_by` and `termination_by'` notation -/
/-! # Support for `termination_by` notation -/
/-- A single `termination_by` clause -/
structure TerminationByElement where
ref : Syntax
declName : Name
vars : Array Syntax
body : Syntax
vars : TSyntaxArray [`ident, ``Lean.Parser.Term.hole]
body : Term
implicit : Bool
deriving Inhabited
/-- `terminatoin_by` clauses, grouped by clique -/
/-- `termination_by` clauses, grouped by clique -/
structure TerminationByClique where
elements : Array TerminationByElement
used : Bool := false
/--
A `termination_by'` or `termination_by` hint, as specified by the user
A `termination_by` hint, as specified by the user
-/
inductive TerminationBy where
/-- `termination_by'` -/
| core (hint : TerminationHint)
/-- `termination_by` -/
| ext (cliques : Array TerminationByClique)
structure TerminationBy where
cliques : Array TerminationByClique
deriving Inhabited
/--
A `termination_by'` or `termination_by` hint, as applicable to a single clique
A `termination_by` hint, as applicable to a single clique
-/
inductive TerminationWF where
| core (stx : Syntax)
| ext (clique : Array TerminationByElement)
abbrev TerminationWF := Array TerminationByElement
/-
open Parser.Command in
/--
Expands the syntax for a `termination_by` clause, checking that
* each function is mentioned once
* each function mentioned actually occurs in the current declaration
@ -125,8 +123,9 @@ def terminationByElement := leading_parser ppLine >> (ident <|> hole) >> many
def terminationBy := leading_parser ppLine >> "termination_by " >> many1chIndent terminationByElement
```
-/
open Parser.Command in
private def expandTerminationByNonCore (hint : Syntax) (cliques : Array (Array Name)) : MacroM TerminationBy := do
def expandTerminationBy? (hint? : Option Syntax) (cliques : Array (Array Name)) :
MacroM TerminationBy := do
let some hint := hint? | return { cliques := #[] }
let `(terminationBy|termination_by $elementStxs*) := hint | Macro.throwUnsupported
let mut alreadyFound : NameSet := {}
let mut elseElemStx? := none
@ -170,51 +169,28 @@ private def expandTerminationByNonCore (hint : Syntax) (cliques : Array (Array N
result := result.push { elements }
if !usedElse && elseElemStx?.isSome then
withRef elseElemStx?.get! <| Macro.throwError s!"invalid `termination_by` syntax, unnecessary else-case"
return TerminationBy.ext result
/--
Expands the syntax for a `termination_by` or `termination_by'` clause, using the appropriate function
-/
def expandTerminationBy (hint? : Option Syntax) (cliques : Array (Array Name)) : MacroM TerminationBy :=
if let some hint := hint? then
if hint.isOfKind ``Parser.Command.terminationByCore then
return TerminationBy.core (← expandTerminationHint hint? cliques)
else if hint.isOfKind ``Parser.Command.terminationBy then
expandTerminationByNonCore hint cliques
else
Macro.throwUnsupported
else
return TerminationBy.core TerminationHint.none
return ⟨result⟩
open Parser.Command in
def TerminationWF.unexpand : TerminationWF → MetaM Syntax
| .ext elements => do
let elementStxs ← elements.mapM fun element => do
let fn : Ident := mkIdent (← unresolveNameGlobal element.declName)
let body : Term := ⟨element.body⟩
let vars : Array Ident := element.vars.map TSyntax.mk
`(terminationByElement|$fn $vars* => $body)
`(terminationBy|termination_by $elementStxs*)
| .core _ => unreachable! -- we don't synthetize termination_by' syntax
def TerminationWF.unexpand (elements : TerminationWF) : MetaM Syntax := do
let elementStxs ← elements.mapM fun element => do
let fn : Ident := mkIdent (← unresolveNameGlobal element.declName)
`(terminationByElement|$fn $element.vars* => $element.body)
`(terminationBy|termination_by $elementStxs*)
def TerminationBy.markAsUsed (t : TerminationBy) (cliqueNames : Array Name) : TerminationBy :=
match t with
| core hint => core (hint.markAsUsed cliqueNames)
| ext cliques => ext <| cliques.map fun clique =>
.mk <| t.cliques.map fun clique =>
if cliqueNames.any fun name => clique.elements.any fun elem => elem.declName == name then
{ clique with used := true }
else
clique
def TerminationBy.find? (t : TerminationBy) (cliqueNames : Array Name) : Option TerminationWF :=
match t with
| core hint => hint.find? cliqueNames |>.map fun v => TerminationWF.core v.value
| ext cliques =>
cliques.findSome? fun clique =>
if cliqueNames.any fun name => clique.elements.any fun elem => elem.declName == name then
some <| TerminationWF.ext clique.elements
else
none
t.cliques.findSome? fun clique =>
if cliqueNames.any fun name => clique.elements.any fun elem => elem.declName == name then
some <| clique.elements
else
none
def TerminationByClique.allImplicit (c : TerminationByClique) : Bool :=
c.elements.all fun elem => elem.implicit
@ -222,19 +198,16 @@ def TerminationByClique.allImplicit (c : TerminationByClique) : Bool :=
def TerminationByClique.getExplicitElement? (c : TerminationByClique) : Option TerminationByElement :=
c.elements.find? (!·.implicit)
def TerminationBy.ensureAllUsed (t : TerminationBy) : MacroM Unit :=
match t with
| core hint => hint.ensureAllUsed
| ext cliques => do
let hasUsedAllImplicit := cliques.any fun c => c.allImplicit && c.used
let mut reportedAllImplicit := true
for clique in cliques do
unless clique.used do
if let some explicitElem := clique.getExplicitElement? then
Macro.throwErrorAt explicitElem.ref "unused termination hint element"
else if !hasUsedAllImplicit then
unless reportedAllImplicit do
reportedAllImplicit := true
Macro.throwErrorAt clique.elements[0]!.ref "unused termination hint element"
def TerminationBy.ensureAllUsed (t : TerminationBy) : MacroM Unit := do
let hasUsedAllImplicit := t.cliques.any fun c => c.allImplicit && c.used
let mut reportedAllImplicit := true
for clique in t.cliques do
unless clique.used do
if let some explicitElem := clique.getExplicitElement? then
Macro.throwErrorAt explicitElem.ref "unused termination hint element"
else if !hasUsedAllImplicit then
unless reportedAllImplicit do
reportedAllImplicit := true
Macro.throwErrorAt clique.elements[0]!.ref "unused termination hint element"
end Lean.Elab.WF

View file

@ -28,23 +28,18 @@ match against a quotation in a command kind's elaborator). -/
@[builtin_term_parser low] def quot := leading_parser
"`(" >> withoutPosition (incQuotDepth (many1Unbox commandParser)) >> ")"
/-
A mutual block may be broken in different cliques,
we identify them using an `ident` (an element of the clique).
We provide two kinds of hints to the termination checker:
1- A wellfounded relation (`p` is `termParser`)
2- A tactic for proving the recursive applications are "decreasing" (`p` is `tacticSeq`)
/--
A decreasing_by clause can either be a single tactic (for all functions), or
a list of tactics labeled with the function they apply to.
-/
def terminationHintMany (p : Parser) := leading_parser
atomic (lookahead (ident >> " => ")) >>
many1Indent (group (ppLine >> ppIndent (ident >> " => " >> p >> patternIgnore (optional ";"))))
def terminationHint1 (p : Parser) := leading_parser p
def terminationHint (p : Parser) := terminationHintMany p <|> terminationHint1 p
def decreasingByElement := leading_parser
ppLine >> ppIndent (ident >> " => " >> Tactic.tacticSeq >> patternIgnore (optional ";"))
def decreasingByMany := leading_parser
atomic (lookahead (ident >> " => ")) >> many1Indent decreasingByElement
def decreasingBy1 := leading_parser Tactic.tacticSeq
def terminationByCore := leading_parser
ppDedent ppLine >> "termination_by' " >> terminationHint termParser
def decreasingBy := leading_parser
ppDedent ppLine >> "decreasing_by " >> terminationHint Tactic.tacticSeq
ppDedent ppLine >> "decreasing_by " >> (decreasingByMany <|> decreasingBy1)
def terminationByElement := leading_parser
ppLine >> (ident <|> Term.hole) >> many (ppSpace >> (ident <|> Term.hole)) >>
@ -53,7 +48,7 @@ def terminationBy := leading_parser
ppDedent ppLine >> "termination_by" >> many1Indent terminationByElement
def terminationSuffix :=
optional (terminationBy <|> terminationByCore) >> optional decreasingBy
optional terminationBy >> optional decreasingBy
@[builtin_command_parser]
def moduleDoc := leading_parser ppDedent <|

View file

@ -8,7 +8,7 @@ options get_default_options() {
// switch to `true` for ABI-breaking changes affecting meta code
opts = opts.update({"interpreter", "prefer_native"}, false);
// switch to `true` for changing built-in parsers used in quotations
opts = opts.update({"internal", "parseQuotWithCurrentStage"}, false);
opts = opts.update({"internal", "parseQuotWithCurrentStage"}, true);
// toggling `parseQuotWithCurrentStage` may also require toggling the following option if macros/syntax
// with custom precheck hooks were affected
opts = opts.update({"quotPrecheck"}, true);

View file

@ -3,7 +3,7 @@ def natPrintAux (n : Nat) (sink : List Char) : List Char :=
if h0 : n < 10
then (n.digitChar :: sink)
else natPrintAux (n / 10) (Nat.digitChar (n % 10) :: sink)
termination_by' measure (fun ⟨n, _⟩ => n)
termination_by _ n => n
decreasing_by sorry
set_option maxRecDepth 100 -- default takes ages in debug mode and triggers stack space threshold

View file

@ -5,4 +5,4 @@ def sum (as : Array Nat) : Nat :=
else
s
go 0 0
termination_by' measure (fun ⟨i, _⟩ => as.size - i)
termination_by go i _ => as.size - i

View file

@ -11,13 +11,9 @@ mutual
else
n
end
termination_by'
invImage
(fun
| PSum.inl ⟨n, true⟩ => (n, 2)
| PSum.inl ⟨n, false⟩ => (n, 1)
| PSum.inr n => (n, 0))
$ Prod.lex sizeOfWFRel sizeOfWFRel
termination_by
f n b => (n, if b then 2 else 1)
g n => (n, 0)
decreasing_by
simp_wf
first

View file

@ -1,9 +1,9 @@
mutwf1.lean:28:2-28:6: error: unsolved goals
mutwf1.lean:24:2-24:6: error: unsolved goals
case h.a
n : Nat
h : n ≠ 0
⊢ Nat.sub n 0 ≠ 0
mutwf1.lean:37:22-37:29: error: failed to prove termination, possible solutions:
mutwf1.lean:33:22-33:29: error: 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

View file

@ -21,8 +21,7 @@ def pack (n: Nat) : List Nat :=
| true => loop (next n) 0 (List.cons acc accs)
| false => loop (next n) (acc+1) accs
loop n 0 []
termination_by'
invImage (fun ⟨n, _, _⟩ => n) Nat.lt_wfRel
termination_by _ n _ _ => n
decreasing_by
simp [invImage, InvImage, Prod.lex, sizeOfWFRel, measure, Nat.lt_wfRel]
apply pack_loop_terminates

View file

@ -11,16 +11,3 @@ termination_by _ n =>
match n with
| _ => n
end Ex2
namespace Ex3
mutual
def isEven : Nat → Bool
| 0 => true
| n+1 => isOdd n
def isOdd : Nat → Bool
| 0 => false
| n+1 => isEven n
end
termination_by' measure (fun n => match n with | PSum.inl n => n | PSum.inr n => n)
end Ex3

View file

@ -6,9 +6,6 @@ mutual
| 0 => false
| n+1 => isEven n
end
termination_by' measure fun
| PSum.inl n => n
| PSum.inr n => n
decreasing_by
simp [measure, invImage, InvImage, Nat.lt_wfRel]
apply Nat.lt_succ_self

View file

@ -6,9 +6,6 @@ mutual
| 0 => false
| n+1 => isEven n
end
termination_by' measure fun
| PSum.inl n => n
| PSum.inr n => n
decreasing_by apply Nat.lt_succ_self
theorem isEven_double (x : Nat) : isEven (2 * x) = true := by
@ -20,7 +17,6 @@ def f (x : Nat) : Nat :=
match x with
| 0 => 1
| x + 1 => f x * 2
termination_by' measure id
decreasing_by apply Nat.lt_succ_self
attribute [simp] f

View file

@ -12,35 +12,6 @@ mutual
| a, b, 0 => b
| a, b, n+1 => f n a b
end
termination_by'
invImage
(fun
| PSum.inl ⟨n, _, _⟩ => (n, 2)
| PSum.inr <| PSum.inl ⟨_, n, _⟩ => (n, 1)
| PSum.inr <| PSum.inr ⟨_, _, n⟩ => (n, 0))
(Prod.lex sizeOfWFRel sizeOfWFRel)
#print f
#print g
#print h
#eval f 5 'a' 'b'
end Ex1
namespace Ex2
mutual
def f : Nat → ααα
| 0, a, b => a
| n, a, b => g a n b |>.1
def g : α → Nat → α → (α × α)
| a, 0, b => (a, b)
| a, n, b => (h a b n, a)
def h : αα → Nat → α
| a, b, 0 => b
| a, b, n+1 => f n a b
end
termination_by
f n _ _ => (n, 2)
g _ n _ => (n, 1)
@ -51,4 +22,4 @@ termination_by
#print h
#eval f 5 'a' 'b'
end Ex2
end Ex1

View file

@ -7,24 +7,9 @@ mutual
| 0 => false
| n+1 => isEven n
end
termination_by' measure fun
| PSum.inl n => n
| PSum.inr n => n
termination_by _ n => n
#print isEven
#print isOdd
#print isEven._mutual
end Ex1
namespace Ex2
mutual
def isEven : Nat → Bool
| 0 => true
| n+1 => isOdd n
def isOdd : Nat → Bool
| 0 => false
| n+1 => isEven n
end
termination_by _ n => n
end Ex2

View file

@ -9,16 +9,13 @@ mutual
| a, n, b => (h a b n, a)
def h : αα → Nat → α
| a, b, 0 => b
| _a, b, 0 => b
| a, b, n+1 => f n a b
end
termination_by'
invImage
(fun
| PSum.inl ⟨n, _, _⟩ => (n, 2)
| PSum.inr <| PSum.inl ⟨_, n, _⟩ => (n, 1)
| PSum.inr <| PSum.inr ⟨_, _, n⟩ => (n, 0))
(Prod.lex sizeOfWFRel sizeOfWFRel)
termination_by
f n _ _ => (n, 2)
g _ n _ => (n, 1)
h _ _ n => (n, 0)
decreasing_by
simp_wf
first
@ -48,14 +45,30 @@ mutual
| a, b, 0 => b
| a, b, n+1 => f n a b
end
termination_by'
invImage
(fun
| PSum.inl ⟨n, _, _⟩ => (n, 2)
| PSum.inr <| PSum.inl ⟨_, n, _⟩ => (n, 1)
| PSum.inr <| PSum.inr ⟨_, _, n⟩ => (n, 0))
(Prod.lex sizeOfWFRel sizeOfWFRel)
termination_by
f n _ _ => (n, 2)
g _ n _ => (n, 1)
h _ _ n => (n, 0)
#print f._unary._mutual
end Ex2
namespace Ex3
mutual
def f : Nat → ααα
| 0, a, b => a
| n, a, b => g a n b |>.1
def g : α → Nat → α → (α × α)
| a, 0, b => (a, b)
| a, n, b => (h a b n, a)
def h : αα → Nat → α
| a, b, 0 => b
| a, b, n+1 => f n a b
end
#print f._unary._mutual
end Ex3

View file

@ -5,7 +5,6 @@ def g (t : Nat) : Nat := match t with
| 0 => 0
| m + 1 => g n
| 0 => 0
termination_by' sorry
decreasing_by sorry
attribute [simp] g

View file

@ -43,7 +43,6 @@ def g (t : Nat) : Nat := match t with
| 0 => 0
| m + 1 => g n
| 0 => 0
termination_by' sorry
decreasing_by sorry
theorem ex1 : g 0 = 0 := by

View file

@ -18,7 +18,7 @@ abbrev P (c : Option Subst) u v := match c with
| none => strangers u v
| some f => act f u = act f v
def rel : WellFoundedRelation (Term × Term) := measure (λ (u, v) => depth u + depth v)
instance rel : WellFoundedRelation (Term × Term) := measure (λ (u, v) => depth u + depth v)
theorem decr_left (l₁ r₁ l₂ r₂ : Term) :
rel.rel (l₁, l₂) (Term.Cons l₁ r₁, Term.Cons l₂ r₂) := by
@ -41,7 +41,7 @@ def robinson (u v : Term) : { f : Option Subst // P f u v } := match u, v with
| .Var i, .Var j =>
if i = j then ⟨ some id, sorry ⟩
else ⟨ some λ n => if n = i then j else n, sorry ⟩
termination_by' invImage (λ ⟨ u, v ⟩ => (u, v)) rel
termination_by _ u v => (u, v)
decreasing_by
first
| apply decr_left _ _ _ _

View file

@ -13,9 +13,6 @@ mutual
| 0 => false
| n+1 => isEven n
end
termination_by' measure fun
| PSum.inl n => n
| PSum.inr n => n
decreasing_by
simp [measure, invImage, InvImage, Nat.lt_wfRel]
apply Nat.lt_succ_self

View file

@ -16,12 +16,9 @@ def h (i j : Nat) : Nat :=
| 0 => g i 0
| Nat.succ j => g i j
end
termination_by'
invImage
(fun
| PSum.inl n => (n, 0)
| PSum.inr n => (n, 1))
(Prod.lex sizeOfWFRel sizeOfWFRel)
termination_by
g i j => (i + j, 0)
h i j => (i + j, 1)
decreasing_by
simp_wf
first

View file

@ -10,7 +10,6 @@ def f (x : Nat) : Nat :=
1
else
f (x - 1) * 2
termination_by' measure id
decreasing_by
apply Nat.pred_lt
exact h

View file

@ -18,13 +18,10 @@ mutual
| a, b, 0 => b
| a, b, n+1 => f n a b
end
termination_by'
invImage
(fun
| PSum.inl ⟨n, _, _⟩ => (n, 2)
| PSum.inr <| PSum.inl ⟨_, n, _⟩ => (n, 1)
| PSum.inr <| PSum.inr ⟨_, _, n⟩ => (n, 0))
(Prod.lex sizeOfWFRel sizeOfWFRel)
termination_by
f n _ _ => (n, 2)
g _ n _ => (n, 1)
h _ _ n => (n, 0)
decreasing_by
simp_wf
first

View file

@ -6,4 +6,4 @@ where
go (i+1) (s + as.get ⟨i, h⟩)
else
s
termination_by' measure (fun ⟨i, s⟩ => as.size - i)
termination_by _ i s => as.size - i

View file

@ -3,7 +3,6 @@ def f (n : Nat) : Nat :=
1
else
2 * f (n-1)
termination_by' measure id
decreasing_by
simp [measure, id, invImage, InvImage, Nat.lt_wfRel]
apply Nat.pred_lt h

View file

@ -13,7 +13,7 @@ namespace Ex3
def heapifyDown (a : Array α) (i : Fin a.size) : Array α :=
have : i < i := sorry
heapifyDown a ⟨i.1, a.size_swap i i ▸ i.2⟩ -- Error, failed to compute motive, `subst` is not applicable here
termination_by' measure fun i => i.1
termination_by _ i => i.1
decreasing_by assumption
end Ex3
@ -34,6 +34,6 @@ def heapifyDown (lt : αα → Bool) (a : Array α) (i : Fin a.size) : Arra
heapifyDown lt a' ⟨j.1.1, a.size_swap i j ▸ j.1.2⟩ -- Error, failed to compute motive, `subst` is not applicable here
else
a
termination_by' measure fun ⟨a, i⟩ => a.size - i.1
termination_by _ a i => a.size - i.1
decreasing_by assumption
end Ex4

View file

@ -5,20 +5,84 @@ mutual
inductive Odd : Nat → Prop
| step : Even n → Odd (n+1)
end
termination_by' measure
termination_by _ n => n -- Error
mutual
def f (n : Nat) :=
if n == 0 then 0 else f (n / 2) + 1
termination_by' measure
termination_by _ => n -- Error
end
mutual
def f (n : Nat) :=
if n == 0 then 0 else f (n / 2) + 1
end
termination_by n => n -- Error
def g' (n : Nat) :=
match n with
| 0 => 1
| n+1 => g' n * 3
termination_by'
h => measure
termination_by
h' n => n -- Error
def g' (n : Nat) :=
match n with
| 0 => 1
| n+1 => g' n * 3
termination_by
g' n => n
_ n => n -- Error
mutual
def isEven : Nat → Bool
| 0 => true
| n+1 => isOdd n
def isOdd : Nat → Bool
| 0 => false
| n+1 => isEven n
end
termination_by
isEven x => x -- Error
mutual
def isEven : Nat → Bool
| 0 => true
| n+1 => isOdd n
def isOdd : Nat → Bool
| 0 => false
| n+1 => isEven n
end
termination_by
isEven x => x
isOd x => x -- Error
mutual
def isEven : Nat → Bool
| 0 => true
| n+1 => isOdd n
def isOdd : Nat → Bool
| 0 => false
| n+1 => isEven n
end
termination_by
isEven x => x
isEven y => y -- Error
mutual
def isEven : Nat → Bool
| 0 => true
| n+1 => isOdd n
def isOdd : Nat → Bool
| 0 => false
| n+1 => isEven n
end
termination_by
isEven x => x
_ x => x
_ x => x + 1 -- Error
namespace Test
mutual
@ -34,8 +98,8 @@ mutual
| 0, a, b => b
| n+1, a, b => f n a b
end
termination_by'
f => measure
g => measure
termination_by
f n => n -- Error
g n => n
end Test

View file

@ -1,4 +1,10 @@
termination_by.lean:8:0-8:23: error: invalid 'termination_by' in mutually inductive datatype declaration
termination_by.lean:13:1-13:24: error: invalid 'termination_by' in 'mutual' block, it must be used after the 'end' keyword
termination_by.lean:21:2-21:3: error: function 'h' not found in current declaration
termination_by.lean:39:2-39:14: error: invalid termination hint element, 'Test.g' and 'Test.f' are in the same clique
termination_by.lean:13:1-13:22: error: invalid 'termination_by' in 'mutual' block, it must be used after the 'end' keyword
termination_by.lean:20:15-20:21: error: function 'n' not found in current declaration
termination_by.lean:28:2-28:11: error: function 'h'' not found in current declaration
termination_by.lean:36:2-36:10: error: invalid `termination_by` syntax, unnecessary else-case
termination_by.lean:47:3-47:16: error: invalid `termination_by` syntax, missing case for function 'isOdd'
termination_by.lean:59:3-59:14: error: function 'isOd' not found in current declaration
termination_by.lean:71:3-71:16: error: invalid `termination_by` syntax, `isEven` case has already been provided
termination_by.lean:84:3-84:15: error: invalid `termination_by` syntax, the else-case (i.e., `_ ... => ...`) has already been specified
termination_by.lean:102:2-102:10: error: invalid `termination_by` syntax, missing case for function 'Test.h'

View file

@ -1,68 +0,0 @@
mutual
def f (n : Nat) :=
if n == 0 then 0 else f (n / 2) + 1
termination_by _ => n
end
mutual
def f (n : Nat) :=
if n == 0 then 0 else f (n / 2) + 1
end
termination_by n => n
def g' (n : Nat) :=
match n with
| 0 => 1
| n+1 => g' n * 3
termination_by
g' n => n
_ n => n
mutual
def isEven : Nat → Bool
| 0 => true
| n+1 => isOdd n
def isOdd : Nat → Bool
| 0 => false
| n+1 => isEven n
end
termination_by
isEven x => x -- Error
mutual
def isEven : Nat → Bool
| 0 => true
| n+1 => isOdd n
def isOdd : Nat → Bool
| 0 => false
| n+1 => isEven n
end
termination_by
isEven x => x
isOd x => x -- Error
mutual
def isEven : Nat → Bool
| 0 => true
| n+1 => isOdd n
def isOdd : Nat → Bool
| 0 => false
| n+1 => isEven n
end
termination_by
isEven x => x
isEven y => y -- Error
mutual
def isEven : Nat → Bool
| 0 => true
| n+1 => isOdd n
def isOdd : Nat → Bool
| 0 => false
| n+1 => isEven n
end
termination_by
isEven x => x
_ x => x
_ x => x + 1 -- Error

View file

@ -1,7 +0,0 @@
termination_by2.lean:4:1-4:22: error: invalid 'termination_by' in 'mutual' block, it must be used after the 'end' keyword
termination_by2.lean:11:15-11:21: error: function 'n' not found in current declaration
termination_by2.lean:20:2-20:10: error: invalid `termination_by` syntax, unnecessary else-case
termination_by2.lean:31:3-31:16: error: invalid `termination_by` syntax, missing case for function 'isOdd'
termination_by2.lean:43:3-43:14: error: function 'isOd' not found in current declaration
termination_by2.lean:55:3-55:16: error: invalid `termination_by` syntax, `isEven` case has already been provided
termination_by2.lean:68:3-68:15: error: invalid `termination_by` syntax, the else-case (i.e., `_ ... => ...`) has already been specified

View file

@ -6,7 +6,6 @@ mutual
| 0 => false
| n+1 => isEven n
end
termination_by' measure fun | PSum.inl n => n | PSum.inr n => n
decreasing_by apply Nat.lt_succ_self
theorem isEven_double (x : Nat) : isEven (2 * x) = true := by

View file

@ -3,4 +3,4 @@ def g (x : Nat) (y : Nat) : Nat :=
2 * g (x-1) y -- Error here
else
0
termination_by' measure (·.1)
termination_by g x y => x

View file

@ -4,7 +4,6 @@ def f (n : Nat) : Nat :=
else
let y := 42
2 * f (n-1)
termination_by' measure id
decreasing_by
simp [measure, id, invImage, InvImage, Nat.lt_wfRel, WellFoundedRelation.rel]
apply Nat.pred_lt h

View file

@ -3,4 +3,4 @@ WellFounded.fix f.proof_1 fun n a =>
if h : n = 0 then 1
else
let y := 42;
2 * a (n - 1) (_ : (measure id).1 (n - 1) n)
2 * a (n - 1) (_ : (invImage (fun a => sizeOf a) instWellFoundedRelation).1 (n - 1) n)