Lean Notes

1.2. Right-associative And (Anne's solution)🔗

Transform a sequence of Ands into a right-associative form (Anne's solution).

Example:

(a ∧ b) ∧ c => a ∧ b ∧ c

Contains versions that use:

  1. assocand% - macrorules

  2. assocandexpr% - Expr only

  3. assocandqq% - Qq

namespace AndAssoc namespace ElabExpr open Lean Meta Elab namespace Lean -- Copied from Lean.Meta.AppBuilder private def infer (h : Expr) : MetaM Expr := do let hType ← inferType h whnfD hType -- Return (a ∧ b) ∧ c ↔ a ∧ (b ∧ c) def mkAndassoc (a b c : Expr) : Expr := mkApp3 (mkConst ``and_assoc) a b c -- Return a ↔ a def mkIffRefl (a : Expr) : MetaM Expr := do let a ← whnf a return mkApp (mkConst ``Iff.refl) a def mkIffTrans (h₁ h₂ : Expr) : MetaM Expr := do let t₁ ← infer h₁ let t₂ ← infer h₂ match t₁.iff?, t₂.iff? with | some (a, b), some (_, c) => return mkApp5 (mkConst ``Iff.trans) a b c h₁ h₂ | none, _ => throwError m!"mkIffTrans: {h₁} is not an Iff" | _, none => throwError m!"mkIffTrans: {h₂} is not an Iff" def mkAndCongr (h₁ h₂ : Expr) : MetaM Expr := do let t₁ ← infer h₁ let t₂ ← infer h₂ match t₁.iff?, t₂.iff? with | some (a, c), some (b, d) => return mkAppN (mkConst ``and_congr) #[a, c, b, d, h₁, h₂] | none, _ => throwError m!"mkAndCongr: {h₁} is not an Iff" | _, none => throwError m!"mkAndCongr: {h₂} is not an Iff" end Lean /-- Given `a` a right associated And, and any `b`, return `(Q, a ∧ b ↔ Q)` where`Q` is `a` with `b` added to the right-most conjunction. Example: merge (x ∧ (y ∧ z)) (x' ∧ y') => (x ∧ (y ∧ (z ∧ (x' ∧ y'))), pf) -/ partial def merge (a b : Expr) : MetaM (Expr × Expr) := do match_expr a with | And a1 a2 => -- a2 ∧ b ↔ right_assoc a2 ∧ b let (a2_b, a2_b_iff) ← merge a2 b -- Q := a1 ∧ (right2_assoc a2 ∧ b) let Q := Lean.mkAnd a1 a2_b -- (a1 ∧ a2) ∧ b ↔ a1 ∧ (a2 ∧ b) let a1_a2_b_iff := Lean.mkAndassoc a1 a2 b -- a1 ↔ a1 let a1Refl ← Lean.mkIffRefl a1 -- a1 ∧ (a2 ∧ b) ↔ a1 ∧ (right_assoc a2 ∧ b) let a1_a2_b_assoc_iff ← Lean.mkAndCongr a1Refl a2_b_iff -- (a1 ∧ a2) ∧ b ↔ a1 ∧ (right_assoc a2 ∧ b) let iff ← Lean.mkIffTrans a1_a2_b_iff a1_a2_b_assoc_iff return (Q, iff) | _ => do let Q := Lean.mkAnd a b let iff ← Lean.mkIffRefl Q return (Q, iff) /-- Transform a sequence of `And`s into a right-associative form, and return a proof that the input and output terms are equivalent. Example: assocAndProof ((a ∧ b) ∧ c) => (a ∧ b ∧ c, pf) : Prop ×' ((a ∧ b) ∧ c) ↔ a ∧ b ∧ c -/ partial def assocAndProof (a : Expr) : MetaM (Expr × Expr) := do match_expr a with | And a1 a2 => -- a1 ↔ right_assoc a1 let (a1_assoc, a1_assoc_iff) ← assocAndProof a1 -- a2 ↔ right_assoc a2 let (a2_assoc, a2_assoc_iff) ← assocAndProof a2 -- a1 ∧ a2 ↔ right_assoc a1 ∧ right_assoc a2 let a1_a2_assoc_iff ← Lean.mkAndCongr a1_assoc_iff a2_assoc_iff -- right_assoc a1 ∧ right_assoc a2 ↔ right_assoc a1 ∧ a2 let (Q, mergeIff) ← merge a1_assoc a2_assoc -- a1 ∧ a2 ↔ right_assoc a1 ∧ a2 let pf ← Lean.mkIffTrans a1_a2_assoc_iff mergeIff return (Q, pf) | _ => do let aRefl ← mkAppM ``Iff.refl #[a] return (a, aRefl) elab "assoc_and_proof* " t:term : term => do let expr ← Term.elabTerm t none let (Q, p) ← assocAndProof expr mkAppM ``PProd.mk #[Q, p] namespace Test variable (a b c d e : Prop) def test1 := assoc_and_proof* a example : (test1 a).fst = a := rfl example : a ↔ a := (test1 a).snd def test2 := assoc_and_proof* (a ∧ b) ∧ c example : (test2 a b c).fst = (a ∧ b ∧ c) := rfl example : ((a ∧ b) ∧ c) ↔ a ∧ b ∧ c := (test2 a b c).snd def test3 := assoc_and_proof* a ∧ (b ∧ c) example : (test3 a b c).fst = (a ∧ b ∧ c) := rfl example : a ∧ (b ∧ c) ↔ a ∧ b ∧ c := (test3 a b c).snd def test4 := assoc_and_proof* ((a ∧ b) ∧ c) ∧ (d ∧ e) example : (test4 a b c d e).fst = (a ∧ b ∧ c ∧ d ∧ e) := rfl example : ((a ∧ b) ∧ c) ∧ (d ∧ e) ↔ a ∧ b ∧ c ∧ d ∧ e := (test4 a b c d e).snd end Test end ElabExpr namespace ElabQq open Lean Meta Elab Qq /-- Given `a` a right associated And, and any `b`, return `(Q, a ∧ b ↔ Q)` where`Q` is `a` with `b` added to the right-most conjunction. Example: merge (x ∧ (y ∧ z)) (x' ∧ y') => (x ∧ (y ∧ (z ∧ (x' ∧ y'))), pf) -/ partial def merge (a b : Q(Prop)) : MetaM ((Q : Q(Prop)) × Q($a ∧ $b ↔ $Q)) := do match a with | ~q($a1 ∧ $a2) => -- a2 ∧ b ↔ right_assoc a2 ∧ b let ⟨a2_b, a2_b_iff⟩ ← merge a2 b -- (a1 ∧ a2) ∧ b ↔ a1 ∧ (right_assoc a2 ∧ b) return ⟨q($a1 ∧ $a2_b), q(Iff.trans and_assoc (and_congr Iff.rfl $a2_b_iff))⟩ | _ => return ⟨q($a ∧ $b), q(Iff.rfl)⟩ /-- Transform a sequence of `And`s into a right-associative form, and return a proof that the input and output terms are equivalent. Example: assocAndProof ((a ∧ b) ∧ c) => (a ∧ b ∧ c, pf) : Prop ×' ((a ∧ b) ∧ c) ↔ a ∧ b ∧ c -/ partial def assocAndProof (a : Q(Prop)) : MetaM ((Q : Q(Prop)) × Q($a ↔ $Q)) := do match a with | ~q($a1 ∧ $a2) => -- a1 ↔ right_assoc a1 let ⟨a1_assoc, a1_iff⟩ ← assocAndProof a1 -- a2 ↔ right_assoc a2 let ⟨a2_assoc, a2_iff⟩ ← assocAndProof a2 -- right_assoc a1 ∧ right_assoc a2 ↔ right_assoc a1 ∧ a2 let ⟨Q, mergeIff⟩ ← merge a1_assoc a2_assoc -- a1 ∧ a2 ↔ right_assoc a1 ∧ a2 return ⟨Q, q(Iff.trans (and_congr $a1_iff $a2_iff) $mergeIff)⟩ | _ => return ⟨a, q(Iff.rfl)⟩ elab "assoc_and_proof_qq* " t:term : term => do let P ← Term.elabTerm t none let ⟨1, ~q(Prop), P⟩ ← inferTypeQ P | throwError m!"{P} is not a Prop" let ⟨Q, p⟩ ← assocAndProof P return q((⟨$Q, $p⟩ : Prop ×' ($P ↔ $Q))) namespace Test variable (a b c d e : Prop) def test1 := assoc_and_proof_qq* a example : (test1 a).fst = a := rfl example : a ↔ a := (test1 a).snd def test2 := assoc_and_proof_qq* (a ∧ b) ∧ c example : (test2 a b c).fst = (a ∧ b ∧ c) := rfl example : ((a ∧ b) ∧ c) ↔ a ∧ b ∧ c := (test2 a b c).snd def test3 := assoc_and_proof_qq* a ∧ (b ∧ c) example : (test3 a b c).fst = (a ∧ b ∧ c) := rfl example : a ∧ (b ∧ c) ↔ a ∧ b ∧ c := (test3 a b c).snd def test4 := assoc_and_proof_qq* ((a ∧ b) ∧ c) ∧ (d ∧ e) example : (test4 a b c d e).fst = (a ∧ b ∧ c ∧ d ∧ e) := rfl example : ((a ∧ b) ∧ c) ∧ (d ∧ e) ↔ a ∧ b ∧ c ∧ d ∧ e := (test4 a b c d e).snd /-- error: 1 is not a Prop -/ #guard_msgs in #check assoc_and_proof_qq* 1 end Test end ElabQq -- A linear version of the elaborator by Anne Baanen namespace ElabQqLinear open Lean Meta Elab Qq /-- Conjunction but the right hand side might be empty. `and? a (some b)` is `a ∧ b`, `and? a none` is `a`. -/ def and? (a : Q(Prop)) : Option Q(Prop) → Q(Prop) | none => a | some b => q($a ∧ $b) /-- Assembles proofs for subproblems into one large proof. -/ def prove (a1 a2 a2_assoc Q : Q(Prop)) (acc : Option Q(Prop)) (a2_iff : Q($(and? a2 acc) ↔ $a2_assoc)) (merge_iff : Q($a1 ∧ $a2_assoc ↔ $Q)) : MetaM Q($(and? q($a1 ∧ $a2) acc) ↔ $Q) := do match acc with | some _ => -- I might also consider extracting the term below as a `lemma`, -- since it is not totally trivial to read what's going on here. return q((and_assoc.trans (and_congr_right' $a2_iff)).trans $merge_iff) | none => return q(Iff.trans (and_congr_right' $a2_iff) $merge_iff) /-- Accumulating version of `assocAndProof`. Reassociate `And`s in `a ∧ acc` to the right, where `acc` is already right-associated. `acc` can potentially be `none` (in which case we reassociate `a` only). -/ partial def assocAcc (a : Q(Prop)) (acc : Option Q(Prop) := none) : MetaM ((Q : Q(Prop)) × Q($(and? a acc) ↔ $Q)) := do match a with | ~q($a1 ∧ $a2) => let ⟨a2_assoc, a2_iff⟩ ← assocAcc a2 acc let ⟨Q, merge_iff⟩ ← assocAcc a1 a2_assoc -- Ideally I'd inline `prove` here but Qq complains about the pattern match... return ⟨Q, ← prove a1 a2 a2_assoc Q acc a2_iff merge_iff⟩ | _ => match acc with | some b => return ⟨q($a ∧ $b), q(Iff.rfl)⟩ | none => return ⟨a, q(Iff.rfl)⟩ elab "assoc_and_proof_qq2* " t:term : term => do let P ← Term.elabTerm t none let ⟨1, ~q(Prop), P⟩ ← inferTypeQ P | throwError m!"{P} is not a Prop" let ⟨Q, p⟩ ← assocAcc P return q((⟨$Q, $p⟩ : Prop ×' ($P ↔ $Q))) namespace Test variable (a b c d e : Prop) def test1_2 := assoc_and_proof_qq2* a example : (test1_2 a).fst = a := rfl example : a ↔ a := (test1_2 a).snd def test2_2 := assoc_and_proof_qq* (a ∧ b) ∧ c example : (test2_2 a b c).fst = (a ∧ b ∧ c) := rfl example : ((a ∧ b) ∧ c) ↔ a ∧ b ∧ c := (test2_2 a b c).snd def test3_2 := assoc_and_proof_qq* a ∧ (b ∧ c) example : (test3_2 a b c).fst = (a ∧ b ∧ c) := rfl example : a ∧ (b ∧ c) ↔ a ∧ b ∧ c := (test3_2 a b c).snd def test4_2 := assoc_and_proof_qq* ((a ∧ b) ∧ c) ∧ (d ∧ e) example : (test4_2 a b c d e).fst = (a ∧ b ∧ c ∧ d ∧ e) := rfl example : ((a ∧ b) ∧ c) ∧ (d ∧ e) ↔ a ∧ b ∧ c ∧ d ∧ e := (test4_2 a b c d e).snd end Test end ElabQqLinear end AndAssoc