Zulip Chat Archive

Stream: new members

Topic: rw inside of List.get


Alex Meiburg (Dec 03 2023 at 00:55):

I have two lists that are equal and I want to simplify a List.get expression using this. My understanding is that rw can't do this, because of the dependent typing of the i argument to get? So, I understand the problem, but not how to resolve it. :) Any help?

mwe of the problem I'm facing (obviously a very silly looking example, but mwe's usually are):

theorem foobar {l₁ l₂ : List } (h : l₁ = l₂) : l₁ = l₂ := by
  apply List.ext_get
  case hl => rw [h]
  intro n hn₁ hn₂
  -- rw [h]
  -- nope, "tactic 'rewrite' failed, motive is not type correct"

  -- simp [h]
  -- nope, "simp made no progress"

  -- conv =>
  --   lhs
  --   arg 1
  -- nope, "cannot select argument"
  sorry

Yongyi Chen (Dec 03 2023 at 01:02):

subst h followed by rfl worked for me. Wonder what subst h; rfl would expand to (rather, what its tactic equivalent would be) without using the subst tactic though.

Alex Meiburg (Dec 03 2023 at 01:03):

ah, that's what I needed! I guess when I use rw I'm really usually just using it for subst. I wasn't aware that subst could "go in" where rw can't. Thanks!

Notification Bot (Dec 03 2023 at 01:03):

Alex Meiburg has marked this topic as resolved.

Kyle Miller (Dec 03 2023 at 01:03):

If subst h doesn't work (because it's an equality between list expressions that aren't variables) you could try congr!, which works here

Alex Meiburg (Dec 03 2023 at 01:03):

It seems like every day I learn a few new tactics that I wish I had known about for a while now :')

Notification Bot (Dec 03 2023 at 01:04):

Alex Meiburg has marked this topic as unresolved.

Kyle Miller (Dec 03 2023 at 01:04):

congr! can "select" arguments that are hard to select. It closes the goal because it uses assumption to try to clean things up.

Alex Meiburg (Dec 03 2023 at 01:05):

oops, yes, this actually isn't what I need. I forgot that subst only works on equalities of variables. Really, I want to rewrite that l1 = a::ls, and then do quite a bit further manipulation... so my mwe doesn't capture either of those issues. I'll try to make a better mwe I guess?

Kyle Miller (Dec 03 2023 at 01:09):

When you have functions that don't work right with simp, you can write @[congr] lemmas to help it rewrite at each argument. Here's a possible one for List.get, but a downside with this particular one is that it inserts Fin.cast expressions, which isn't great.

import Mathlib.Tactic

@[congr]
theorem List.congr_get {α : Type*} {l₁ l₂ : List α} (h : l₁ = l₂)
    {i₁ i₂ : Fin l₁.length} (hi : i₁ = i₂) :
    l₁.get i₁ = l₂.get (i₂.cast congr(length $h)) := by
  subst_vars
  rfl

theorem foobar {l₁ l₂ : List } (h : l₁ = l₂) : l₁ = l₂ := by
  apply List.ext_get
  case hl => rw [h]
  intro n hn₁ hn₂
  simp_rw [h]
  /-
  ⊢ List.get l₂ (Fin.cast _ { val := n, isLt := hn₁ }) = List.get l₂ { val := n, isLt := hn₂ }
  -/

Kyle Miller (Dec 03 2023 at 01:12):

I guess you could also use this congr lemma, which could be better, since it ought to at least let simp eliminate the Fin.cast a bit earlier (though it won't in your example).

@[congr]
theorem List.congr_get {α : Type*} {l₁ l₂ : List α} (h : l₁ = l₂)
    {i₁ : Fin l₁.length} {i₂ : Fin l₂.length} (hi : i₁.cast congr(length $h) = i₂) :
    l₁.get i₁ = l₂.get i₂ := by
  subst_vars
  rfl

(I didn't know this was a legal congr lemma until now!)

Alex Meiburg (Dec 03 2023 at 01:19):

Well, I can't say I totally understand why that lets congr solve things it otherwise couldn't, but cool thanks! :) It seems like it does resolve my issue, I think...

Notification Bot (Dec 03 2023 at 01:19):

Alex Meiburg has marked this topic as resolved.

Kyle Miller (Dec 03 2023 at 01:28):

@Alex Meiburg It's not for congr, but for simp (and simp_rw). Here's a model for you for how simp does its rewriting. Say you want to run simp on a goal e. What simp's job is is to come up with an equality e = e' with e' the result of simplification, and then it rewrites e using this equality.

So, it sets up the goal e = ?e' with the RHS a metavariable, and then it does the following recursive procedure. It finds an appropriate "congruence lemma" given specific features of e (it is able to generate its own, but you can give it custom congruence lemmas like I did for List.get when it needs help). Then it essentially uses apply with this congruence lemma. This results in some number of hypotheses (two in the case of List.congr_get) of the same x = ?x' form, corresponding to sub-parts of e, and then it recurses.

Kyle Miller (Dec 03 2023 at 01:30):

Somewhere in here it uses @[simp] lemmas to actually come up with simplifications to particular subexpressions, rather than just using the congruence lemmas to navigate into the subexpressions.

Notification Bot (Dec 03 2023 at 01:36):

Alex Meiburg has marked this topic as unresolved.

Alex Meiburg (Dec 03 2023 at 01:36):

I see, okay. I'm still struggling with anything involving List.get, I think the dependent types or something are just making my life miserable. My state looks like this (modulo some irrelevant hypotheses):

α: Type u_2
inst: Semiring α
P: α[X]
n: 
hn: n < natDegree P + 1
this: natDegree P  1
hn₂: ¬n = 0
 coeff P
    (List.get (List.reverse (List.range (natDegree P + 1)))
      { val := n, isLt := (_ : n < List.length (List.reverse (List.range (natDegree P + 1)))) }) =
  List.get (leadingCoeff P :: List.replicate (natDegree P) 0)
    { val := n, isLt := (_ : n < Nat.succ (List.length (List.replicate (natDegree P) 0))) }

There's all these nice lemmas about List.get, like List.get_reverse, but I can't seem to apply any of them. Or like, if I have that n=0, then I would like to use List.get_cons_zero since I have List.get (... :: ...) { val := 0, ... }, but I can't seem to apply it. The congr lemma you shared fixed some issues but it's still saying no every step of the way...

I think maybe I'm just fundamentally ignorant about how to handle things when the Fin argument to get are dependent on the list, and then the Fin.isLt is dependent on the Fin.val... ugh

Alex Meiburg (Dec 03 2023 at 01:36):

I see, okay. I'm still struggling with anything involving List.get, I think the dependent types or something are just making my life miserable. My state looks like this (modulo some irrelevant hypotheses):

α: Type u_2
inst: Semiring α
P: α[X]
n: 
hn: n < natDegree P + 1
this: natDegree P  1
hn₂: ¬n = 0
 coeff P
    (List.get (List.reverse (List.range (natDegree P + 1)))
      { val := n, isLt := (_ : n < List.length (List.reverse (List.range (natDegree P + 1)))) }) =
  List.get (leadingCoeff P :: List.replicate (natDegree P) 0)
    { val := n, isLt := (_ : n < Nat.succ (List.length (List.replicate (natDegree P) 0))) }

There's all these nice lemmas about List.get, like List.get_reverse, but I can't seem to apply any of them. Or like, if I have that n=0, then I would like to use List.get_cons_zero since I have List.get (... :: ...) { val := 0, ... }, but I can't seem to apply it. The congr lemma you shared fixed some issues but it's still saying no every step of the way...

I think maybe I'm just fundamentally ignorant about how to handle things when the Fin argument to get are dependent on the list, and then the Fin.isLt is dependent on the Fin.val... ugh

Alex Meiburg (Dec 03 2023 at 01:37):

At this point I don't even know how to make a good mwe. I can't even use the proof state as an example because of the underscores in the goal... and I don't know how to handle those :(

Kyle Miller (Dec 03 2023 at 01:38):

Could you use docs#List.get! instead of List.get so that you could avoid dependent types?

Alex Meiburg (Dec 03 2023 at 01:45):

Maybe. I really want to use List.ext_get, and there doesn't seem to be a List.ext_get! -- yet. (Say that three times quickly...) Maybe I'll just prove a List.ext_get! and then use that. Doesn't seem like it should be too bad! And then I can avoid this madness haha

Yongyi Chen (Dec 03 2023 at 01:50):

By the way, here's another way I found that's really tricky (path induction):

induction h with
| refl => rfl

Yongyi Chen (Dec 03 2023 at 01:51):

I think this is similar to subst h under the hood, but they're different in one way: induction uses Eq.rec while subst uses Eq.ndrec (at least in this case).

Notification Bot (Dec 03 2023 at 01:55):

Yongyi Chen has marked this topic as unresolved.

Kyle Miller (Dec 03 2023 at 01:56):

@Yongyi Chen Is that different from doing cases h? That's a common trick for handling the case when h is an equality involving constructors

Yongyi Chen (Dec 03 2023 at 01:56):

Using by? I see that the expansion of cases h also uses Eq.ndrec but also wraps the expression in an Eq.casesOn.

Kyle Miller (Dec 03 2023 at 01:58):

(I think the big difference between subst and cases is that the latter does "dependent elimination", meaning it can handle more equalities than just those with a free variable on one side. induction is very similar to cases, maybe even the same in what it can do)

Yongyi Chen (Dec 03 2023 at 01:59):

Makes sense, subst h and rcases h with rfl expand to the same thing. So rcases (or cases) is more general.

Kyle Miller (Dec 03 2023 at 02:00):

Is there some inference about subst vs induction that you're drawing regarding Eq.rec vs Eq.ndrec? Or is that a difference you noticed between them?

Yongyi Chen (Dec 03 2023 at 02:01):

I noticed the difference using by?. I don't claim to understand too much.

Yongyi Chen (Dec 03 2023 at 02:04):

Induction doesn't seem to be too happy in this example:

example {a b : } (ha : a < 10) (hb : b < 10) (h : Fin.mk a ha = Fin.mk b hb) : a = b := by
  induction h with
  /-
  index in target's type is not a variable (consider using the `cases` tactic instead)
    { val := b, isLt := hb }
  -/
  | refl => sorry

Kyle Miller (Dec 03 2023 at 02:07):

Ah, ok, induction has a subst-like restriction. (The "indices" for the Eq type are the x and y in x = y). It's cases that knows how to carry out dependent elimination, where it sees that h is the same as a = b and ha = hb (though ha and hb are propositions, so it might just ignore them)

Kyle Miller (Dec 03 2023 at 02:08):

(I don't really know what dependent elimination is beyond having experience with Lean's error messages that complain when cases can't do it.)

Alex Meiburg (Dec 03 2023 at 02:10):

The first step to sanity

/-- If two lists are the same length and get! is the same on all indices, the lists are equal. -/
theorem List.ext_get! [Inhabited α] (hl : length l₁ = length l₂)
    (h :  n, get! l₁ n = get! l₂ n) : l₁ = l₂ :=
  ext fun n => by
      cases h₃ : get? l₁ n <;> cases h₄ : get? l₂ n
      case none.none => rfl
      case none.some => exfalso; linarith [get?_eq_none.mp h₃, (get?_eq_some.mp h₄).1]
      case some.none => exfalso; linarith [get?_eq_none.mp h₄, (get?_eq_some.mp h₃).1]
      case some.some => congr; exact (get!_of_get? h₃)  (get!_of_get? h₄)  h n

Last updated: Dec 20 2023 at 11:08 UTC