Zulip Chat Archive

Stream: lean4

Topic: termination by


Joseph O (Feb 16 2022 at 01:16):

i need a termination by but i dont really know how they work. i need it for this function:

def allPairsAux (xs : List α) (ys : List β) (accum : List (α × β)) : List (α × β) :=
  match xs, ys with
  | [], [] => accum
  | _::xss, [] => allPairsAux xss ys accum
  | [], _::yss => allPairsAux xs yss accum
  | x::xs, y::ys =>
    allPairsAux (x::xs) ys ((x, y)::accum)

Chris B (Feb 16 2022 at 01:28):

I think this is actually kind of an advanced example because it's not on one argument:

def allPairsAux (xs : List α) (ys : List β) (accum : List (α × β)) : List (α × β) :=
  match hx:xs, hy:ys with
  | [], [] => accum
  | _::xss, [] =>
    have h0 : xss.length + ys.length < xss.length + 1 := by simp [hy, Nat.lt_succ_self]
    allPairsAux xss ys accum
  | [], _::yss =>
    have h0 : xs.length + yss.length < yss.length + 1 := by simp [hx, Nat.lt_succ_self]
    allPairsAux xs yss accum
  | x::xs, y::ys => allPairsAux (x::xs) ys ((x, y)::accum)
  termination_by _ _ xs ys _ => xs.length + ys.length

Joseph O (Feb 16 2022 at 01:33):

Chris B said:

I think this is actually kind of an advanced example because it's not on one argument:

def allPairsAux (xs : List α) (ys : List β) (accum : List (α × β)) : List (α × β) :=
  match hx:xs, hy:ys with
  | [], [] => accum
  | _::xss, [] =>
    have h0 : xss.length + ys.length < xss.length + 1 := by simp [hy, Nat.lt_succ_self]
    allPairsAux xss ys accum
  | [], _::yss =>
    have h0 : xs.length + yss.length < yss.length + 1 := by simp [hx, Nat.lt_succ_self]
    allPairsAux xs yss accum
  | x::xs, y::ys => allPairsAux (x::xs) ys ((x, y)::accum)
  termination_by _ _ xs ys _ => xs.length + ys.length

how is the termination by working there? and why are the haves nessacery?

Chris B (Feb 16 2022 at 01:36):

They're not strictly necessary, the syntax for termination hints allows for a decreasing_by <tactic proof>clause to discharge the proof obligations, but I think decreasing_by has issues when more than one branch produces a unique obligation. But in any case you have to show that something being passed as an argument is strictly decreasing with each recursive call, where decreasing is defined in terms of some instance of WellFoundedRelation.

Chris B (Feb 16 2022 at 01:37):

For each branch I think it looks in the local assumptions for a proof first, which is why the have thing works.

Joseph O (Feb 16 2022 at 01:40):

ah yes

Joseph O (Feb 16 2022 at 01:41):

how does the by simp thing work?

Joseph O (Feb 16 2022 at 01:42):

also, what is hx:xs and hy:ys?

Chris B (Feb 16 2022 at 01:47):

hx and hy are hypotheses. The by simp parts are tactic proofs, simp is a tactic that applies lemmas; it's applying the stuff in the brackets.

Chris B (Feb 16 2022 at 01:54):

Well I guess more accurately it's like syntax for a match that preserves the hypotheses in the match arms. So like in the first case you need a witness that hy = [].

Joseph O (Feb 16 2022 at 01:57):

how can i debug print values in the middle of a function?

Joseph O (Feb 16 2022 at 02:01):

Nevermind, i found it

Joseph O (Feb 16 2022 at 02:09):

i do have a question about my function:

def allPairsAux (xs : List α) (ys : List β) (accum : List (α × β)) : List (α × β) :=
  match hx:xs, hy:ys with
  | [], [] => accum.reverse
  | _::xs, [] =>
    have h0 : xs.length + ys.length < xs.length + 1 := by simp [hy, Nat.lt_succ_self]
    allPairsAux xs ys accum
  | [], _::ys =>
    have h0 : xs.length + ys.length < ys.length + 1 := by simp [hx, Nat.lt_succ_self]
    allPairsAux xs ys accum
  | x::xs, y::ys =>
    allPairsAux (x::xs) ys ((x, y)::accum)

lets say i input ['a', 'b'] [1, 2, 3] []. Here is how the iterations should go:

['a', 'b'] [1, 2, 3] []
['a', 'b'] [2, 3] [('a', 1)]
['a', 'b'] [3] [('a', 2), ('a', 1)]
['a', 'b'] [] [('a', 3), ('a', 2), ('a', 1)]
['b'] [1, 2, 3] [('a', 3), ('a', 2), ('a', 1)] -- this is what it should turn into, because it matches at the second case, but for some reason this is not happening
-- same process

this is the output: [("a", 1), ("a", 2), ("a", 3)]

i wish there was a way i could debug and print the values

Joseph O (Feb 16 2022 at 02:10):

do you have an idea why this could be happening @Chris B ?

Chris B (Feb 16 2022 at 02:18):

Because the version you just posted is now explicitly reversing the list in the nil/nil case.

Reid Barton (Feb 16 2022 at 02:24):

What do you expect allPairsAux ['a', 'b'] [] [('a', 3), ('a', 2), ('a', 1)] to be?

Joseph O (Feb 16 2022 at 02:37):

well it shouldn't return accum.reverse since it doesnt match the [] [] case

Joseph O (Feb 16 2022 at 02:42):

why is it matching on that then @Chris B

Reid Barton (Feb 16 2022 at 02:54):

It isn't matching the [] [] case.

Chris B (Feb 16 2022 at 03:01):

def allPairsAux (xs : List α) (ys : List β) (accum : List (α × β)) : List (α × β) :=
  match hx:xs, hy:ys with
  | [], [] => accum
  | _::xss, [] =>
    have h0 : xss.length + ys.length < xss.length + 1 := by simp [hy, Nat.lt_succ_self]
    allPairsAux xss ys accum
  | [], _::yss =>
    have h0 : xs.length + yss.length < yss.length + 1 := by simp [hx, Nat.lt_succ_self]
    allPairsAux xs yss accum
  | x::xs, y::ys => allPairsAux (x::xs) ys ((x, y)::accum)
  termination_by _ _ xs ys _ => xs.length + ys.length

-- [('a', 3), ('a', 2), ('a', 1)]
#eval allPairsAux ['a', 'b'] [1, 2, 3] []
def allPairsAux (xs : List α) (ys : List β) (accum : List (α × β)) : List (α × β) :=
  match hx:xs, hy:ys with
  | [], [] => accum.reverse
  | _::xss, [] =>
    have h0 : xss.length + ys.length < xss.length + 1 := by simp [hy, Nat.lt_succ_self]
    allPairsAux xss ys accum
  | [], _::yss =>
    have h0 : xs.length + yss.length < yss.length + 1 := by simp [hx, Nat.lt_succ_self]
    allPairsAux xs yss accum
  | x::xs, y::ys => allPairsAux (x::xs) ys ((x, y)::accum)
  termination_by _ _ xs ys _ => xs.length + ys.length

-- [('a', 1), ('a', 2), ('a', 3)]
#eval allPairsAux ['a', 'b'] [1, 2, 3] []

Chris B (Feb 16 2022 at 03:02):

The only thing I changed is the accum.reverse in the nil/nil case.

Joseph O (Feb 16 2022 at 03:15):

Yes, but that is not the output i am looking for

Joseph O (Feb 16 2022 at 03:16):

What i meant here:

['a', 'b'] [1, 2, 3] []
['a', 'b'] [2, 3] [('a', 1)]
['a', 'b'] [3] [('a', 2), ('a', 1)]
['a', 'b'] [] [('a', 3), ('a', 2), ('a', 1)]
['b'] [1, 2, 3] [('a', 3), ('a', 2), ('a', 1)] -- this is what it should turn into, because it matches at the second case, but for some reason this is not happening
-- same process

Is not the output, but it what allPairsAux should be taking as its arguments on its next iteration

Joseph O (Feb 16 2022 at 03:17):

The output should actually be

[(a, 1), (a,  2), (a, 3),  (b, 1), (b, 2), (b, 3)]

Sorry for not making myself clear

Chris B (Feb 16 2022 at 03:34):

That's just not the function you wrote, I don't know what to tell you. In the case that ys is empty and xs is not, you're calling the function recursively with the tail of xs and the empty list.

Chris B (Feb 16 2022 at 03:34):

But you seem to expect ys to become [1, 2, 3] again.

Arthur Paulino (Feb 16 2022 at 03:40):

I think it's easier if you write an auxiliary function that receives a : α and l : List β and then returns something of type List (α × β). Such that the return of 2 and ["a", "b", "c"] is [(2, "a"), (2, "b"), (2, "c")].

Then you can do a foldl on a List α using your function

Arthur Paulino (Feb 16 2022 at 03:49):

Unless, of course, you want to go through the exercise of building a standalone function. Then you can ignore my idea

Joseph O (Feb 16 2022 at 12:33):

Arthur Paulino said:

I think it's easier if you write an auxiliary function that receives a : α and l : List β and then returns something of type List (α × β). Such that the return of 2 and ["a", "b", "c"] is [(2, "a"), (2, "b"), (2, "c")].

Then you can do a foldl on a List α using your function

An auxiliary function for my auxiliary function?

Joseph O (Feb 16 2022 at 12:34):

Chris B said:

That's just not the function you wrote, I don't know what to tell you. In the case that ys is empty and xs is not, you're calling the function recursively with the tail of xs and the empty list.

I wanted it to reset to the original ys

Joseph O (Feb 16 2022 at 12:34):

that would solve the whole problem

Joseph O (Feb 16 2022 at 12:35):

Arthur Paulino said:

I think it's easier if you write an auxiliary function that receives a : α and l : List β and then returns something of type List (α × β). Such that the return of 2 and ["a", "b", "c"] is [(2, "a"), (2, "b"), (2, "c")].

Then you can do a foldl on a List α using your function

Why would I want to use fold? Doesn’t fold turn a sequence into a value

Joseph O (Feb 16 2022 at 12:36):

if I could hold the original values somewhere

Yakov Pechersky (Feb 16 2022 at 12:53):

That "value" can be another sequence.

Joseph O (Feb 16 2022 at 12:59):

Yakov Pechersky said:

That "value" can be another sequence.

And that sequence holds the original two lists?

Yakov Pechersky (Feb 16 2022 at 13:07):

You can build up a tuple as an aux function, and use prod.snd to get your final value. Check out accumulators in Haskell too.

Joseph O (Feb 16 2022 at 13:07):

i tried using a Product,

def allPairsAux (xs : List α) (ys : List β) (accum : List (α × β)) (origs: (List α × List β)) : List (α × β) :=
  match hx:xs, hy:ys with
  | [], [] => accum
  | _::xs, [] =>
    have h0 : xs.length + ys.length < xs.length + 1 := by simp [hy, Nat.lt_succ_self]
    allPairsAux xs (origs.2) accum origs
  | [], _::ys =>
    have h0 : xs.length + ys.length < ys.length + 1 := by simp [hx, Nat.lt_succ_self]
    allPairsAux (origs.1) ys accum origs
  | x::xs, y::ys =>
    allPairsAux (x::xs) ys ((x, y)::accum) origs

but i get the error

tactic 'assumption' failed,
α : Type u_1
β : Type u_2
xs : List α
ys : List β
accum : List (α × β)
origs : List α × List β
x : α
xs : List α
hx : xs = x :: xs
hy : ys = []
h0 : List.length xs + List.length ys < List.length xs + 1
 List.length origs.snd + List.length accum < List.length accum

its pretty weird in my opinion

Joseph O (Feb 16 2022 at 13:12):

What could have cuased the assumption to fail?

Reid Barton (Feb 16 2022 at 13:38):

well, it's definitely not true that List.length origs.snd + List.length accum < List.length accum, right?

Reid Barton (Feb 16 2022 at 13:40):

I suggest you start out with a partial def so you can make sure the definition is correct first without worrying about termination

Joseph O (Feb 16 2022 at 13:40):

I have to prove that too?

Joseph O (Feb 16 2022 at 13:40):

Reid Barton said:

I suggest you start out with a partial def so you can make sure the definition is correct first without worrying about termination

ok

Joseph O (Feb 16 2022 at 13:41):

Reid Barton said:

well, it's definitely not true that List.length origs.snd + List.length accum < List.length accum, right?

where can i use that?

Joseph O (Feb 16 2022 at 14:07):

Any ideas???

Alexander Bentkamp (Feb 16 2022 at 14:31):

The last version you posted does not terminate:

partial def allPairsAux (xs : List α) (ys : List β) (accum : List (α × β)) (origs: (List α × List β)) : List (α × β) :=
  match hx:xs, hy:ys with
  | [], [] => accum
  | _::xs, [] =>
    have h0 : xs.length + ys.length < xs.length + 1 := by simp [hy, Nat.lt_succ_self]
    allPairsAux xs (origs.2) accum origs
  | [], _::ys =>
    have h0 : xs.length + ys.length < ys.length + 1 := by simp [hx, Nat.lt_succ_self]
    allPairsAux (origs.1) ys accum origs
  | x::xs, y::ys =>
    allPairsAux (x::xs) ys ((x, y)::accum) origs

#eval allPairsAux [1] [] [] ([1],[1])  -- Does not terminate.

Reid is right. Try to add partial like I did here and test the function using #eval until it does what you want it to do. Then you can attempt to prove termination.

Joseph O (Feb 16 2022 at 14:59):

i tried this but its the same thign

def allPairsAux (xs : List α) (ys : List β) (accum : List (α × β)) (origs: (List α × List β)) : List (α × β) :=
  match hx:xs, hy:ys with
  | [], [] => accum
  | _::xs, [] =>
    have h0 : xs.length + ys.length < xs.length + 1 := by simp [hy, Nat.lt_succ_self]
    allPairsAux xs (origs.snd) accum origs
  | [], _::ys =>
    have h0 : xs.length + ys.length < ys.length + 1 := by simp [hx, Nat.lt_succ_self]
    allPairsAux (origs.fst) ys accum origs
  | x::xs, y::ys =>
    allPairsAux (x::xs) ys ((x, y)::accum) origs

termination_by _ _ xs ys accum origs => (origs.snd).length + accum.length < accum.length

Joseph O (Feb 16 2022 at 14:59):

EDIT: fixed

Arthur Paulino (Feb 16 2022 at 15:01):

Isn't your server dead? CTRL+SHIFT+X sometimes brings it back to life

Joseph O (Feb 16 2022 at 15:02):

It was because I was evalutating a function that didnt terminate

Joseph O (Feb 16 2022 at 15:02):

something keeps crashing vscode though

Joseph O (Feb 16 2022 at 15:03):

and I really dont know how to fix the termination. Im going to admit it

Alexander Bentkamp (Feb 16 2022 at 15:04):

Here's my attempt at it (at least of what I think you are trying to define):

def allPairs (xs : List α) (ys : List β) : List (α × β) := xs.bind (fun x => ys.map (fun y => (x, y)))

Joseph O (Feb 16 2022 at 15:19):

Ah you used map. My goal was ultimately to use functions like map and fold as little as possible. But you know, its much esier. Also, what does xs.bind do?

Joseph O (Feb 16 2022 at 15:21):

I have no idea how the code you wrote works

Mario Carneiro (Feb 16 2022 at 15:31):

l.bind f is (l.map f).join, where join flattens a list of lists

Mario Carneiro (Feb 16 2022 at 15:32):

that is, you apply the function to each element of the list, and then concatenate all the resulting lists (the function returns a list for each element)

Alexander Bentkamp (Feb 16 2022 at 15:38):

Maybe it's easier to understand if I write it using two auxiliary definitions:

def attach (x : α) (ys : List β) : List (α × β) := ys.map (fun y => (x, y))

#eval attach 2 ["a","b","c"]
-- [(2, "a"), (2, "b"), (2, "c")]

def allPairsAux (xs : List α) (ys : List β) : List (List (α × β)) := xs.map (fun x => attach x ys)

#eval allPairsAux [1,2,3] ["a","b","c"]
-- [[(1, "a"), (1, "b"), (1, "c")], [(2, "a"), (2, "b"), (2, "c")], [(3, "a"), (3, "b"), (3, "c")]]

def allPairs (xs : List α) (ys : List β) : List (α × β) := (allPairsAux xs ys).join

#eval allPairs [1,2,3] ["a","b","c"]
-- [(1, "a"), (1, "b"), (1, "c"), (2, "a"), (2, "b"), (2, "c"), (3, "a"), (3, "b"), (3, "c")]

Mario Carneiro (Feb 16 2022 at 15:39):

Here's a version using only primitive operations on list:

def allPairs (xs : List α) (ys : List β) : List (α × β) :=
  let rec aux
  | [], ys, r => r
  | x::xs, ys, r =>
    let rec aux₂
    | [], r => r
    | y::ys, r => (x, y) :: r
    aux₂ ys (aux xs ys r)
  aux xs ys []

Mario Carneiro (Feb 16 2022 at 15:40):

Here's another version that should work but causes a kernel type check error (cc: @Leonardo de Moura)

def allPairs (xs : List α) (ys : List β) : List (α × β) :=
  let rec aux
  | [], r => r
  | x::xs, r =>
    let rec aux₂
    | [], r => r
    | y::ys, r => (x, y) :: r
    aux₂ ys (aux xs r)
  aux xs []
  (kernel) declaration has free variables 'allPairs.aux'

Leonardo de Moura (Feb 16 2022 at 15:43):

Mario Carneiro said:

Here's another version that should work but causes a kernel type check error (cc: Leonardo de Moura)

def allPairs (xs : List α) (ys : List β) : List (α × β) :=
  let rec aux
  | [], r => r
  | x::xs, r =>
    let rec aux₂
    | [], r => r
    | y::ys, r => (x, y) :: r
    aux₂ ys (aux xs r)
  aux xs []

  (kernel) declaration has free variables 'allPairs.aux'

Thanks for reporting the issue. I will take a look.

Arthur Paulino (Feb 16 2022 at 15:44):

I love it when bugs are found by casually playing with a system

Mario Carneiro (Feb 16 2022 at 15:45):

I think they call it "monkey testing"

Leonardo de Moura (Feb 16 2022 at 15:47):

https://github.com/leanprover/lean4/issues/1020

Arthur Paulino (Feb 16 2022 at 15:50):

Mario Carneiro said:

I think they call it "monkey testing"

I knew that terminology for random tests equipped with brutally fast inputs (easily applicable for testing apps with GUIs). Which makes me wonder if it's possible to apply this technique to attack the Lean kernel in various ways in search for bugs like this

Mario Carneiro (Feb 16 2022 at 15:52):

that sounds more like fuzz testing (which would probably work fairly well against the lean kernel)

Joseph O (Feb 16 2022 at 16:50):

Thanks everyone


Last updated: Dec 20 2023 at 11:08 UTC