Zulip Chat Archive

Stream: lean4

Topic: terminating recursion on recursive list-like structure


Jared green (Aug 05 2024 at 16:55):

im trying to define a function depth on variadic, but it fails to prove termination.

import Init.Prelude
import Init.Coe
import Mathlib.Data.List.Basic

variable {α : Type}
inductive IList α
  where
  | single : α -> IList α
  | cons : α -> IList α -> IList α

def IList.fold  (l : IList α)  (f : α -> b -> b)  (g : α -> b) : b :=
  match l with
  | single a => g a
  | cons a as => f a (fold as f g)

def IList.map  (f : α -> b) : IList α -> IList b
  | single a => single (f a)
  | cons a as => cons (f a) (map f as)

def IList.toList : IList α -> {l : List α // l  []}
  | single a =>  [a], by simp
  | cons a as =>  (a :: (IList.toList as)),by simp

def IList.ofList (l: {l : List α // l  []}) : IList α :=
  match l with
  |  [x], _  => .single x
  |  x :: xs@(y :: ys),h => .cons x ( IList.ofList  xs, fun h => by aesop  )

instance : Coe (IList α) {l : List α // l  []} where
  coe l := IList.toList l

instance : Coe {l : List α // l  []} (IList α) where
  coe l := IList.ofList l

[h : DecidableEq α]
inductive variadic (α :Type) [h : DecidableEq α]
(pred : α -> Prop)
  where
  | vatom : α -> (variadic α pred)
  | vAnd : IList (variadic α pred) -> variadic α pred
  | vOr : IList (variadic α pred) -> variadic α pred
  | vNot : variadic α pred -> variadic α pred
--deriving DecidableEq

namespace variadic

 def depth (v : variadic α pred) : Nat :=
  match v with
  | vatom _ => 1
  | vAnd l => (IList.fold l (fun x y => max (depth x) y) (fun y => depth y)) + 1
  | vOr l => (IList.fold l (fun x y => max (depth x) y) (fun y => depth y)) + 1
  | vNot a => depth a + 1

Shreyas Srinivas (Aug 05 2024 at 17:02):

I suggest encoding depth into the variadic

Shreyas Srinivas (Aug 05 2024 at 17:03):

I am guessing lean can't see any obvious reason why max (depth x) y is decreasing.

Jared green (Aug 05 2024 at 17:04):

is that the only way? if the other ways are too complicated, i guess i will do that.

Shreyas Srinivas (Aug 05 2024 at 17:05):

I see it as the easy way out. Can you come up with a parameter that is decreasing in the recursion?

Shreyas Srinivas (Aug 05 2024 at 17:05):

I can't

Shreyas Srinivas (Aug 05 2024 at 17:06):

The recursive call is under a binder

Jared green (Aug 05 2024 at 17:06):

i thought maybe the definition of variadic could be changed some other way

Shreyas Srinivas (Aug 05 2024 at 17:08):

That's a whole other can of worms. I am just suggesting a way to salvage this definition with some extra additions.

Henrik Böving (Aug 05 2024 at 17:09):

If you define something along the lines of docs#List.attach and use it before the fold this is entirely possible.

Henrik Böving (Aug 05 2024 at 17:10):

Alternativley if you don't want to prove stuff about this particular function just give it a partial def

Jared green (Aug 05 2024 at 17:11):

@Henrik Böving how would that look?

Henrik Böving (Aug 05 2024 at 17:11):

What part?

Jared green (Aug 05 2024 at 17:11):

list.attach

Henrik Böving (Aug 05 2024 at 17:13):

inductive Tree (α : Type) where
| tip (x : α) : Tree α
| node (xs : List (Tree α)) : Tree α

def Tree.sum (t : Tree Nat) : Nat :=
  match t with
  | tip x => x
  | node xs => xs.attach.foldl (init := 0) (fun acc x, h => acc + x.sum)
termination_by t

Henrik Böving (Aug 05 2024 at 17:14):

The attach makes it such that Lean can see underneath the binder that the x is a member of xs and thus the function decreases

Henrik Böving (Aug 05 2024 at 17:15):

The terminaton_by is also optional in this case, Lean can see it on its own

Jared green (Aug 05 2024 at 17:16):

about implementing attach?

Henrik Böving (Aug 05 2024 at 17:18):

You can click the source button to see how it's done. Though it does require doing some additional effort yourself. I would suggest not to implement your own IList inductive and instead use an abbreviation for a List subtype

Jared green (Aug 05 2024 at 17:20):

like, first convert .toList.1?

Henrik Böving (Aug 05 2024 at 17:21):

I would suggest to not have IList at all and instead define abbrev ILIst (a : Type) : Type := {l : List α // l ≠ []}`

Jared green (Aug 05 2024 at 17:22):

then i would lose the ability to pattern match in the way i need.

Edward van de Meent (Aug 05 2024 at 17:25):

you could replace IList a -> with a -> List a ->, possibly?

Jared green (Aug 05 2024 at 17:26):

@Edward van de Meent what does that do?

Edward van de Meent (Aug 05 2024 at 17:27):

it means you get to use the preexisting List infrastructure, particularly  docs#List.attach which Henrik mentioned

Edward van de Meent (Aug 05 2024 at 17:28):

and you still can match.

Jared green (Aug 05 2024 at 17:28):

how would that look?

Henrik Böving (Aug 05 2024 at 17:29):

abbrev IList (α : Type) : Type := { xs : List α // xs  [] }

@[match_pattern]
def IList.single (x : α) : IList α := [x], by simp

@[match_pattern]
def IList.cons (x : α) (xs : IList α) : IList α := x :: xs, by simp

def IList.isSingle (xs : IList α) : Bool :=
  match xs with
  | IList.single x => true
  | IList.cons x xs => false

it's a bit disappointing that this doesn't work

Henrik Böving (Aug 05 2024 at 17:31):

abbrev IList (α : Type) : Type := { xs : List α // xs  [] }



def IList.isSingle (xs : IList α) : Bool :=
  match xs with
  | [x], _⟩ => true
  | x :: xs, _⟩ => false

though doing it by hand does very much work, so you can just pattern match

Edward van de Meent (Aug 05 2024 at 17:54):

turns out, all you really need is a bit of mutual recursion following the structure of your inductive:

import Init.Prelude
import Init.Coe
import Mathlib.Data.List.Basic

variable {α : Type}

inductive variadic (α :Type) (pred : α -> Prop)
  where
  | vatom : α -> (variadic α pred)
  | vAnd : variadic α pred  List (variadic α pred) -> variadic α pred
  | vOr : variadic α pred  List (variadic α pred) -> variadic α pred
  | vNot : variadic α pred -> variadic α pred


namespace variadic

mutual

def list_variadic_depth (l : List (variadic α pred)) : Nat :=
  match l with
    | [] => 0
    | v :: l => max (depth' v) (list_variadic_depth l)

def depth' (v : variadic α pred) : Nat :=
  match v with
    | vatom _ => 1
    | vAnd v l => (max (depth' v) (list_variadic_depth l)) + 1
    | vOr v l => (max (depth' v) (list_variadic_depth l)) + 1
    | vNot v => depth' v + 1

end

Edward van de Meent (Aug 05 2024 at 17:55):

let's see if i can get it to work with IList too...

Jared green (Aug 05 2024 at 17:56):

@Edward van de Meent see if it can be done with it as abbreviation, as suggested by henrik

Edward van de Meent (Aug 05 2024 at 17:59):

are you getting the kernel error too?

Jared green (Aug 05 2024 at 18:00):

yes

Edward van de Meent (Aug 05 2024 at 18:02):

ah wait i know what's going on. you can't fix that, because you can't mention predicates on your type in an argument for your inductive type

Edward van de Meent (Aug 05 2024 at 18:03):

i.e. this is an illegal constructor:

inductive T
 | foo (T -> Sort _) -> T

Edward van de Meent (Aug 05 2024 at 18:04):

i think your best bet would be either switching to the version with List or switching to an inductive definition of IList

Jared green (Aug 05 2024 at 18:05):

go with the inductive version

Edward van de Meent (Aug 05 2024 at 18:12):

import Init.Prelude
import Init.Coe
import Mathlib.Data.List.Basic

variable {α : Type}

inductive IList α
  where
  | single : α -> IList α
  | cons : α -> IList α -> IList α

inductive variadic (α :Type) (pred : α -> Prop)
  where
  | vatom : α -> (variadic α pred)
  | vAnd : IList (variadic α pred) -> variadic α pred
  | vOr : IList (variadic α pred) -> variadic α pred
  | vNot : variadic α pred -> variadic α pred

namespace variadic

mutual
def ilist_depth : ( IList (variadic α pred))  Nat
  | .single v => variadic_depth v
  | .cons v l => max (variadic_depth v) (ilist_depth l)


def variadic_depth : variadic α pred  Nat
  | .vatom _ => 1
  | .vAnd l => ilist_depth l + 1
  | .vOr l => ilist_depth l + 1
  | .vNot v => variadic_depth v + 1

end

Jared green (Aug 05 2024 at 20:04):

how do i get the theorem that the depth of an ilist variadic is at least that of each element?

Henrik Böving (Aug 05 2024 at 20:06):

can you state the theorem

Jared green (Aug 05 2024 at 20:12):

import Init.Prelude
import Init.Coe
import Mathlib.Data.List.Basic

variable {α : Type}
inductive IList α
  where
  | single : α -> IList α
  | cons : α -> IList α -> IList α

def IList.fold  (l : IList α)  (f : α -> b -> b)  (g : α -> b) : b :=
  match l with
  | single a => g a
  | cons a as => f a (fold as f g)

def IList.map  (f : α -> b) : IList α -> IList b
  | single a => single (f a)
  | cons a as => cons (f a) (map f as)

def IList.toList : IList α -> {l : List α // l  []}
  | single a =>  [a], by simp
  | cons a as =>  (a :: (IList.toList as)),by simp


--universe u
variable  {α : Type}[h : DecidableEq α]
inductive variadic (α :Type) [h : DecidableEq α]
(pred : α -> Prop)
  where
  | vatom : α -> (variadic α pred)
  | vAnd : IList (variadic α pred) -> variadic α pred
  | vOr : IList (variadic α pred) -> variadic α pred
  | vNot : variadic α pred -> variadic α pred
--deriving DecidableEq

namespace variadic

mutual
def ilist_depth : ( IList (variadic α pred))  Nat
  | .single v => variadic_depth v
  | .cons v l => max (variadic_depth v) (ilist_depth l)

def variadic_depth : variadic α pred  Nat
  | .vatom _ => 1
  | .vAnd l => ilist_depth l + 1
  | .vOr l => ilist_depth l + 1
  | .vNot v => variadic_depth v + 1

end
theorem depth_le_elem :  l : IList (variadic α pred), x  l.toList.1, variadic_depth x   ilist_depth l :=
  by
  intro l x hx
  induction' l a a as hx
  unfold ilist_depth
  have h: x = a := by {
    unfold IList.toList at hx
    sorry
  }
  sorry

Edward van de Meent (Aug 05 2024 at 20:36):

the basic idea for such a proof is useing the inductive definition of l or x or both, since these functions are defined that way. in this case we only need induction on l:

theorem depth_le_elem :  l : IList (variadic α pred),  x  l.toList.1,
    variadic_depth x  ilist_depth l := by
  intro l
  induction l with
    | single a =>
      intro x
      dsimp [IList.toList,ilist_depth]
      simp only [List.mem_singleton]
      rintro rfl
      exact Nat.le_refl x.variadic_depth
    | cons a l hind =>
      intro x
      dsimp [IList.toList,ilist_depth]
      simp only [List.mem_cons]
      rintro (rfl|r)
      · exact Nat.le_max_left x.variadic_depth (ilist_depth l)
      · exact hind x r |>.trans  <| Nat.le_max_right a.variadic_depth (ilist_depth l)

Edward van de Meent (Aug 05 2024 at 20:38):

if you need induction over both variables/definitions, you will probably want to separate your theorem out in two just like the definitions, and prove them in a mutual block.

Edward van de Meent (Aug 05 2024 at 20:38):

not the case here, but if you run into these kinds of "how do i work with inductive definitions" issues

Jared green (Aug 05 2024 at 20:40):

all those exacts get a type mismatch.

Edward van de Meent (Aug 05 2024 at 20:41):

that's odd... let me take a look

Edward van de Meent (Aug 05 2024 at 20:42):

no issue for me... although you are missing a in the statement of the theorem...

Edward van de Meent (Aug 05 2024 at 20:44):

immediately after the first comma

Jared green (Aug 05 2024 at 20:45):

i copied what you gave me. on the first one, it says , has type x.variadic_depth ≤ x.variadic_depth, but is expected to have type x.variadic_depth ≤ ilist_depth (IList.single x)

Edward van de Meent (Aug 05 2024 at 20:47):

that's odd...
please do try to reproduce it in the lean playground, it works for me there.

Edward van de Meent (Aug 05 2024 at 20:50):

for the sake of completeness, this is what it should look like

Large Code Block

Jared green (Aug 05 2024 at 20:51):

yes, but it isnt working in my editor.

Kevin Buzzard (Aug 05 2024 at 20:52):

Then you may have an out of date mathlib

Edward van de Meent (Aug 05 2024 at 20:52):

from the error, it would seem that the dsimp [ilist_depth] isn't firing?

Edward van de Meent (Aug 05 2024 at 20:52):

because that should be reduced at that point...

Edward van de Meent (Aug 05 2024 at 20:53):

i don't think this is a mathlib version issue...

Jared green (Aug 05 2024 at 20:53):

@Kevin Buzzard odd, i updated it yesterday

Edward van de Meent (Aug 05 2024 at 20:55):

could you try replacing dsimp with rw in the single section of the induction?

Kevin Buzzard (Aug 05 2024 at 20:56):

Mathlib has updated to Lean 4.11.0-rc1 since yesterday.

Jared green (Aug 05 2024 at 20:56):

that works for both dsimps.

Kevin Buzzard (Aug 05 2024 at 20:57):

Indeed I can confirm that the code doesn't work with mathlib-from-a-few-days-ago but works fine with today's mathlib.

Edward van de Meent (Aug 05 2024 at 20:57):

huh

Edward van de Meent (Aug 05 2024 at 20:57):

how does that happen???

Jared green (Aug 05 2024 at 20:57):

im on rev # 1f03949ae423ae9fb010318e73f8969e44288c92

Jared green (Aug 05 2024 at 20:58):

lean v4.10.0

Kevin Buzzard (Aug 05 2024 at 20:59):

Indeed I can confirm that it doesn't work with that version for me. But it works with today's mathlib so there's no problem here :-)

Edward van de Meent (Aug 05 2024 at 21:06):

i'm curious what change caused/fixed this issue?

Jared green (Aug 06 2024 at 17:34):

how do i get DecidableEq for variadic? i need the proof as follows, but i dont know how to finish it.

def dec_eq_IList : DecidableEq α -> DecidableEq IList α :=
  by
  intro ha
  unfold DecidableEq
  intro a b
  have h : (a = b) <-> (a.toList.1 = b.toList.1) := by {
    sorry
  }
  rw [h]
  apply instDecidableEqList

Edward van de Meent (Aug 06 2024 at 17:36):

for IList, simply deriving DecidableEq seems to work...

Jared green (Aug 06 2024 at 17:37):

how does that look?

Edward van de Meent (Aug 06 2024 at 17:37):

inductive IList α
  where
  | single : α -> IList α
  | cons : α -> IList α -> IList α
deriving DecidableEq

Jared green (Aug 06 2024 at 17:45):

if i couldnt do that? (im updating now so i can)

Edward van de Meent (Aug 06 2024 at 17:47):

otherwise, you can simply pattern match on constructors and use if h : a = b then Decidable.isTrue (by simpa) else Decidable.isFalse (by simpa) when the constructors match, otherwise simply Decidable.isFalse (by simp)

Edward van de Meent (Aug 06 2024 at 17:47):

should do it

Edward van de Meent (Aug 06 2024 at 17:55):

out of curiosity, why is pred part of the signature of variadic while it doesn't seem to occur in the signature of constructors of variadic in any useful way?

Jared green (Aug 06 2024 at 17:57):

i dont know what you mean

Jared green (Aug 06 2024 at 17:58):

if you mean its on another line, that was so i could comment out the decidableeq

Edward van de Meent (Aug 06 2024 at 17:59):

i mean, why is it there at all? it doesn't get used?

Jared green (Aug 06 2024 at 18:00):

it does get used

Edward van de Meent (Aug 06 2024 at 18:00):

not in the constructors...

Jared green (Aug 06 2024 at 18:01):

in other functions i define elsewhere

Edward van de Meent (Aug 06 2024 at 18:03):

if it's not part of the signature of the constructors, surely there is no point in including it at all?

Jared green (Aug 06 2024 at 18:07):

i consider it necessary, as i have to be able to convert it to prop, because of certain properties i need to prove.

Edward van de Meent (Aug 06 2024 at 18:08):

allright then...

Jared green (Aug 06 2024 at 20:17):

so apparently deriving DecidableEq on IList didnt get decidableEq on variadic on its own.


Last updated: May 02 2025 at 03:31 UTC