Zulip Chat Archive
Stream: lean4
Topic: Termination Checker Question
Aaron Eline (May 03 2024 at 14:12):
Ran into an unexpected (to me) failure of the termination checker, I was curious for more information about how the checker worked.
Here's a simple lean file that fails to compile:
import Std.Data.Nat
import Std.Data.List
inductive Expr where
| lit : Nat -> Expr
| add : Expr -> Expr -> Expr
| mult : List Expr -> Expr
def sum (ns : List Nat) : Nat :=
match ns with
| [] => 0
| n :: ns => n + (sum ns)
def sum_of_lits (e : Expr) : Nat :=
match e with
| .lit n => n
| .add e1 e2 => (sum_of_lits e1) + (sum_of_lits e2)
| .mult es => sum (List.map sum_of_lits es)
It fails to being unable to prove termination of the recursive call to sum_of_lits
as an argument to List.map
.
In Coq, the equivalent file:
Require Import Coq.Lists.List.
Open Scope list_scope.
Import ListNotations.
Inductive expr : Type :=
| Num : nat -> expr
| Add : expr -> expr -> expr
| Mult : list expr -> expr.
Fixpoint sum (ns : list nat) : nat :=
match ns with
| [] => 0
| (x::xs) => x + (sum xs)
end.
Fixpoint sum_all_literals (e : expr) : nat :=
match e with
| Num n => n
| Add e1 e2 => (sum_all_literals e1) + (sum_all_literals e2)
| Mult es => sum (map sum_all_literals es)
end.
Which compiles fine. I was curious about the differences in the termination checkers, is this a fundamental limitation of a lean design decision?
Henrik Böving (May 03 2024 at 15:24):
The difference between how termination is checked in lean and coq is quite drastical. The specific case that you have at hand is a nested inductive type which is not currently supported very well when it comes to automation sadly.
Joachim Breitner (May 03 2024 at 15:39):
That said, this is (one way) to make it work:
import Std.Data.Nat
import Std.Data.List
inductive Expr where
| lit : Nat -> Expr
| add : Expr -> Expr -> Expr
| mult : List Expr -> Expr
def sum (ns : List Nat) : Nat :=
match ns with
| [] => 0
| n :: ns => n + (sum ns)
def sum_of_lits (e : Expr) : Nat :=
match e with
| .lit n => n
| .add e1 e2 => (sum_of_lits e1) + (sum_of_lits e2)
| .mult es => sum (List.map (fun ⟨e, _⟩ => sum_of_lits e) (List.attach es))
Aaron Eline (May 03 2024 at 15:40):
Joachim Breitner said:
That said, this is (one way) to make it work:
Could you explain the List.attach
?
Henrik Böving (May 03 2024 at 15:46):
It turns some list xs
of a
s into a list of a
s that have a proof that they are a member of xs
. This proof is enough to let the automation see that there is a well founded recursion going on.
Aaron Eline (May 03 2024 at 15:47):
Henrik Böving said:
It turns some list
xs
ofa
s into a list ofa
s that have a proof that they are a member ofxs
. This proof is enough to let the automation see that there is a well founded recursion going on.
Ah cool, thanks a bunch
Joachim Breitner (May 04 2024 at 06:04):
Also see https://lean-lang.org/blog/2024-1-11-recursive-definitions-in-lean/, section on nested recursion
Last updated: May 02 2025 at 03:31 UTC