Zulip Chat Archive

Stream: general

Topic: termination_by case wise


Bhakti Shah (Jun 27 2023 at 21:15):

This is probably really dumb but i've been confused for a bit: I have two mutually recursive functions and i need to specify termination. The structure is something like

mutual
def outer (a : Expr) : Except A B := match a with
| C1 m => -- not a recursive call
| C2 m => do
let x <- outer m
ok x
| C3 m => do -- m : List Expr
let x <- inner m
ok x
... variations of the three

def inner (a : List Expr) : Except A C :=
if a == [] then ok []
else do
let h <- outer a.head
let t <- inner a.tail
ok head :: tail

proving termination for inner is simple as List.length a is decreasing.
the only way i can think of proving termination for outer is by pattern matching on each Expr constructor. for constructors with an argument that is a collection type the decreasing size seems simple enough.
this is the dumb part of the question but how do i prove termination for constructors matching the structure ofC1, that is, no recursive calls? It seems intuitive that hitting that case means terminating right away because we just return immediately but i have no idea how to say something is actually decreasing. I'm assuming the termination checker doesn't usually complain about this case but if i'm doing a match statement in the termination_by clause i need to specify a relation for each arm.
this is the slightly less dumb part but how do i prove termination for a C2 type? in my head it's just if the internal call to outer terminates then that arm terminates but i'm not sure how to specify that.

thank you :)

Mario Carneiro (Jun 27 2023 at 22:08):

@Bhakti Shah Could you make a #mwe?

Mario Carneiro (Jun 27 2023 at 22:11):

It sounds to me like this is an induction on sizeOf

Mario Carneiro (Jun 27 2023 at 22:11):

which is also the default

Mario Carneiro (Jun 27 2023 at 22:12):

but it would probably simplify the termination proof significantly if you used pattern matching in inner instead of if-else

Mario Carneiro (Jun 27 2023 at 22:12):

def inner (a : List Expr) : Except A C :=
match a with
| [] => ok []
| h :: t => do ok (<- outer h) :: (<- inner t)

Bhakti Shah (Jun 28 2023 at 16:07):

sorry about that, here's an mwe:

inductive Expr where
| C1 (m : Nat)
| C2 (m : Expr)
| C3 (m : List Expr)
deriving instance Inhabited for Expr

mutual
  def outer (a : Expr) : Except Bool Nat := match a with
  | .C1 m => .error false
  | .C2 m => do
  let x <- outer m
  .ok x
  | .C3 m => do
  let x <- inner m
  .ok x[0]!

  def inner (a : List Expr) : Except Bool (List Nat) :=
  match a with
  | [] => .ok []
  | _ => do
  .ok ((<- outer a[0]!) :: (<- inner a.tail!))
end
termination_by
outer a => match a with
| .C1 m => sizeOf m
| .C2 m => sizeOf m
| .C3 m => List.length m
inner a => List.length a

This is a far simpler case than what I have but I can't prove termination on this either. I'm not sure if I'm using sizeOf correctly, though

I guess I understand why it's refusing to accept termination (the C2 case), but I'm not sure how to do it (or even if it can be done) in a case where you have something like this.

Sebastian Ullrich (Jun 28 2023 at 16:15):

As Mario said, this is an induction on sizeOf, at least if you correctly match on the list in inner

mutual
  def outer (a : Expr) : Except Bool Nat := match a with
  | .C1 m => .error false
  | .C2 m => do
  let x <- outer m
  .ok x
  | .C3 m => do
  let x <- inner m
  .ok x[0]!

  def inner (a : List Expr) : Except Bool (List Nat) :=
  match a with
  | [] => .ok []
  | x::xs => do
  .ok ((<- outer x) :: (<- inner xs))
end

Bhakti Shah (Jun 28 2023 at 17:07):

that does in fact seem to work for this simple case, though not for the more complex one I have. I'll try to mess with the pattern matching a bit more before i ask about that, though. Thank you!
[edit: It was in fact just a matter of switching out a couple other head and tail instances :)]


Last updated: Dec 20 2023 at 11:08 UTC