Zulip Chat Archive

Stream: mathlib4

Topic: List.destutter'


Yury G. Kudryashov (Nov 10 2024 at 05:46):

I'm looking at @Alex Meiburg 's #9082 and I wonder why docs#List.destutter' works as a special case of docs#List.destutter . I think that we'll get better lemmas if we drop a from the answer. What do you think?

Yury G. Kudryashov (Nov 10 2024 at 05:48):

E.g., with the current definition we have

theorem tail_destutter'_congr (h :  c, R a c  R b c) :
    (l.destutter' R a).tail = (l.destutter' R b).tail := by

and we'll be able to drop tail if we drop the head from the output of List.destutter'.

Yury G. Kudryashov (Nov 10 2024 at 05:49):

AFAICT, this is an auxiliary definition which is only used to prove properties of List.destutter, so I assume that we can change its definition without worrying about backwards compatibility too much.

Eric Rodriguez (Nov 10 2024 at 23:45):

Yeah, it was much easier to develop the theory with having destutter' around, much like docs#List.Chain'. At least, that was my experience whilst writing it.

Yury G. Kudryashov (Nov 11 2024 at 02:35):

Another question: since we don't hardcode R = \ne, should we call it something like List.greedyChain instead of destutter?

Yury G. Kudryashov (Nov 11 2024 at 02:35):

It can be used, e.g., to construct monotone chains too.

Yury G. Kudryashov (Nov 11 2024 at 04:39):

Another question: should we rename docs#List.Chain' to Chain and docs#List.Chain to something else?

Violeta Hernández (Nov 12 2024 at 03:21):

Yury G. Kudryashov said:

Another question: should we rename docs#List.Chain' to Chain and docs#List.Chain to something else?

That would be great, I'm tired of having to fight the docPrime linter every time I write anything involving pairwise relations...

Violeta Hernández (Nov 12 2024 at 03:23):

Maybe docs#List.Chain can be List.ChainWith?

Yury G. Kudryashov (Nov 12 2024 at 03:23):

In fact, I don't understand why we need it at all.

Yury G. Kudryashov (Nov 12 2024 at 03:26):

We can define

def List.IsChain (R : α  α  Prop) (l : List α) : Prop :=
  (l.zipWith R l.tail).Forall

or

def List.IsChain (R : α  α  Prop) (l : List α) : Prop :=
  l.dropLast.Forall₂ R l.tail

Yury G. Kudryashov (Nov 12 2024 at 03:26):

Then List.Chain' R a l = List.IsChain R (a :: l)

Violeta Hernández (Nov 12 2024 at 03:32):

Well, List.Chain R a l is already the same as List.Chain' R (a :: l), so its only value is being easier to prove things about

Violeta Hernández (Nov 12 2024 at 03:33):

The main difference in my experience is that docs#List.chain_cons is nicer than docs#List.chain'_cons or docs#List.chain'_cons'

Yury G. Kudryashov (Nov 12 2024 at 03:34):

It will become List.isChain_cons_cons.

Yury G. Kudryashov (Nov 12 2024 at 03:34):

(if we drop one of 2 predicates)

Violeta Hernández (Nov 12 2024 at 03:40):

Yury G. Kudryashov said:

We can define

def List.IsChain (R : α  α  Prop) (l : List α) : Prop :=
  (l.zipWith R l.tail).Forall

or

def List.IsChain (R : α  α  Prop) (l : List α) : Prop :=
  l.dropLast.Forall₂ R l.tail

These re-definitions seem harder to reason about and possibly worse in terms of performance. What about something like this instead?

inductive List.IsChain (R : α  α  Prop) : List α  Prop
  | nil : List.IsChain R []
  | singleton (a : α) : List.IsChain R [a]
  | cons_cons {a b : α} (l : List α) : R a b  List.IsChain R (b :: l)  List.IsChain R (a :: b :: l)

Violeta Hernández (Nov 12 2024 at 03:48):

Actually nevermind, performance isn't really a factor here since this is a function into Prop and not into Bool or anything of the sort.

Violeta Hernández (Nov 12 2024 at 03:49):

Still, I feel an inductive definition is probably going to be the easiest to work with no matter what. We can prove equivalences to the zipWith, etc. definitions separately.

Kim Morrison (Nov 12 2024 at 03:50):

@Mario Carneiro / @François G. Dorais, any opinions here? If we're going to change the definition of Chain I'd prefer to do to sooner rather than later.

Yury G. Kudryashov (Nov 12 2024 at 04:15):

We can prove API lemmas starting with any definition. Since it's computationally irrelevant, I don't care much about which one we use.

Yury G. Kudryashov (Nov 12 2024 at 04:15):

Possibly, an inductive one is better, because Lean won't try to unify it with something else.

François G. Dorais (Nov 12 2024 at 04:20):

Just to be sure: the two alternatives are?

Yury G. Kudryashov (Nov 12 2024 at 04:21):

I gave 2 similar definitions of List.IsChain in terms of zipWith, @Violeta Hernández wrote a direct inductive definition. Also, there is a status quo (Chain and Chain').

Yury G. Kudryashov (Nov 12 2024 at 04:21):

I don't like status quo, because List.Chain R a l is just List.Chain' R (a :: l).

François G. Dorais (Nov 12 2024 at 04:41):

There seems to be two issues:

  • List.Chain : α → List α → Prop is begging for a nonempty list type as input.
  • List.Chain': List α → Prop is the right notion for a possibly empty list.

Violeta Hernández (Nov 12 2024 at 04:42):

There's also the less important but still relevant issue of having a primed name.

François G. Dorais (Nov 12 2024 at 04:45):

As far as the definitions of List.IsChain, this is probably more efficient:

def List.IsChain (R : α  α  Prop) : List α  Prop
  | [] | [_] => True
  | x::xs@(y::_) => R x y  IsChain R xs

But Violeta's inductive definition is perhaps easier to reason with.

François G. Dorais (Nov 12 2024 at 04:46):

Violeta Hernández said:

There's also the less important but still relevant issue of having a primed name.

Prime names are convenient in the short term but never good in the long term.

Violeta Hernández (Nov 12 2024 at 04:47):

François G. Dorais said:

As far as the definitions of List.IsChain, this is probably more efficient:

def List.IsChain (R : α  α  Prop) : List α  Prop
  | [] | [_] => True
  | x::xs@(y::_) => R x y  IsChain xs

But Violeta's inductive definition is perhaps easier to reason with.

I think yours might be better actually, since it makes List.IsChain.cons_cons an iff trivially.

François G. Dorais (Nov 12 2024 at 04:48):

But "efficiency" is not so relevant unless R is decidable.

Violeta Hernández (Nov 12 2024 at 04:50):

I think your recursive definition should also make it easier to prove decidability of IsChain (for decidable relations)

François G. Dorais (Nov 12 2024 at 04:50):

@Kim Morrison I support replacing List.Chain with the current List.Chain'.

Violeta Hernández (Nov 12 2024 at 04:52):

Pinging @Bhavik Mehta since he recently added some Chain lemmas in one of my PRs for golfing purposes

François G. Dorais (Nov 12 2024 at 04:52):

@Violeta Hernández But your inductive definition comes with an inductive principle, which is nice to have.

François G. Dorais (Nov 12 2024 at 05:13):

Note that this is actually tail recursive:

def List.isChain (r : α  α  Bool) : List α  Bool
  | [] | [_] => true
  | x::xs@(y::_) => r x y && isChain r xs

Yury G. Kudryashov (Nov 12 2024 at 05:16):

Is it? I mean, does Lean treat && so that this definition becomes tail recursive?

François G. Dorais (Nov 12 2024 at 05:17):

Yes, because && is short circuiting, so if r x y = false then isChain r xs is not evaluated.

Yury G. Kudryashov (Nov 12 2024 at 05:18):

BTW, can we somehow ask Lean whether it treats a given definition as tail recursive?

François G. Dorais (Nov 12 2024 at 05:19):

I think Mario wrote some code for that but I just inspect the IR.

François G. Dorais (Nov 12 2024 at 05:21):

set_option trace.compiler.ir.result true in
def List.isChain (r : α  α  Bool) : List α  Bool
  | [] | [_] => True
  | x::xs@(y::_) => r x y && isChain r xs

The key is at the end:

      Bool.true 
        let x_11 : u8 := List.isChain._rarg x_1 x_4;
        ret x_11

François G. Dorais (Nov 12 2024 at 06:12):

Found it! https://leanprover.zulipchat.com/#narrow/channel/348111-batteries/topic/tail.20recursion/near/404515501

Bhavik Mehta (Nov 12 2024 at 09:03):

I find List.Chain useful as it is, I don't think it's just there to make the definition of List.Chain' any easier. Indeed when proving things about lists, I often find myself copying the Chain/Chain' pattern in order to make the proofs go more smoothly, having support to induct on a list without changing its first element is pretty useful! So I'm in favour of keeping it, since I use List.Chain quite a bit more than I use List.Chain'. But really if we keep the induction principles that Chain currently has, phrased in terms of Chain', then I'd be happy too.

Yury G. Kudryashov (Nov 12 2024 at 16:54):

You can induct on l in IsChain R (a :: l).

Yury G. Kudryashov (Nov 12 2024 at 16:54):

I'll try to make some proof-of-concept PR tonight or tomorrow.


Last updated: May 02 2025 at 03:31 UTC