Zulip Chat Archive
Stream: lean4
Topic: Matching Recursive Subtypes
Mac (Jun 20 2022 at 22:15):
For Lake reasons, I am constructing a "well-formed" version of the Lean.Name
type. It works fine, but I feel my lack of experience with Lean techniques for thereon proving is making my approach less ergonomic than it should be. For example, here is a relevant #mwe:
namespace Lean
namespace Name
inductive WellFormed : Name → Prop
| anonymousWff : WellFormed anonymous
| strWff {n p s} : WellFormed p → n = mkStr p s → WellFormed n
| numWff {n p v} : WellFormed p → n = mkNum p v → WellFormed n
def WellFormed.elimStr : WellFormed (Name.str p s h) → WellFormed p
| strWff w h => by
simp [mkStr] at h
rewrite [h.1.symm] at w
exact w
def WellFormed.elimNum : WellFormed (Name.num p v h) → WellFormed p
| numWff w h => by
simp [mkNum] at h
rewrite [h.1.symm] at w
exact w
end Name
abbrev WfName :=
{n : Name // n.WellFormed}
namespace WfName
def anonymous : WfName :=
⟨Name.anonymous, Name.WellFormed.anonymousWff⟩
def mkStr : WfName → String → WfName
| ⟨p, h⟩, s => ⟨Name.mkStr p s, Name.WellFormed.strWff h rfl⟩
def mkNum : WfName → Nat → WfName
| ⟨p, h⟩, v => ⟨Name.mkNum p v, Name.WellFormed.numWff h rfl⟩
open Syntax in
protected def quote : WfName → Syntax
| ⟨n, w⟩ => match n with
| .anonymous => mkCIdent ``anonymous
| .str p s _ => mkCApp ``mkStr #[WfName.quote ⟨p, w.elimStr⟩, quote s]
| .num p v _ => mkCApp ``mkNum #[WfName.quote ⟨p, w.elimNum⟩, quote v]
instance : Quote WfName := ⟨WfName.quote⟩
In particular, I feel like I could be using match pattern or something in quote
to have Lean deduce the fact that p
is a proper WfName
for free, but I am not sure how. Attempting to mark anonymous
, mkStr
, and mkNum
as @[matchPattern]
and use them directly in the match
does not work and produces the following error:
invalid patterns, `p` is an explicit pattern variable, but it only occurs in positions that are inaccessible to pattern matching
{ val := Name.str .(p.1) s .(mixHash (hash p.val) (hash s)),
property := (_ : Name.WellFormed (Name.str .(p.1) .(s) .(mixHash (hash p.val) (hash s)))) }
Any ideas on how I could improve my code?
Mac (Jun 20 2022 at 22:20):
Also, as a side note, in quote
the linter reports w
as an used variable despite it clearly being used in the code (e.g., w.elimStr
, w.elimNum
).
Mario Carneiro (Jun 20 2022 at 23:35):
I don't think your quote
definition can be improved much, but your elimStr
theorem can be:
inductive WellFormed : Name → Prop
| anonymous : WellFormed anonymous
| str {n p s} : WellFormed p → n = mkStr p s → WellFormed n
| num {n p v} : WellFormed p → n = mkNum p v → WellFormed n
theorem WellFormed.elimStr : WellFormed (Name.str p s h) → WellFormed p
| str w rfl => w
theorem WellFormed.elimNum : WellFormed (Name.num p v h) → WellFormed p
| num w rfl => w
Mario Carneiro (Jun 20 2022 at 23:36):
(I removed the Wff
suffix, since the convention is to use the namespace for disambiguation here)
Mario Carneiro (Jun 20 2022 at 23:37):
you could have a definition which packages up ⟨p, w.elimStr⟩
to make it a bit shorter
Mario Carneiro (Jun 20 2022 at 23:40):
But if you are hoping for a way to get mkStr
and mkNum
directly out of a pattern match, I think you will be disappointed. While it is possible to get something approximately like this, you would have to match on w
to do this, not p
; but matching on w
would not allow you to construct data. The fundamental issue is that mkStr
and mkNum
can be arbitrary functions here, they are not obviously nonoverlapping. Matching on p
has the desired codegen and the elimStr
theorem allows you to patch things up after the fact.
Mario Carneiro (Jun 20 2022 at 23:42):
One other thing you could do is provide an induction principle that does the pattern match on p
and patches things up
Mario Carneiro (Jun 21 2022 at 00:00):
variable {motive : WfName → Sort u}
(anonymous : motive .anonymous)
(str : ∀ p s, motive p → motive (p.mkStr s))
(num : ∀ p n, motive p → motive (p.mkNum n)) in
def rec : ∀ p, motive p
| ⟨n, w⟩ => match n with
| .anonymous => anonymous
| .str p s d =>
(match w with | .str w rfl => rfl : mkStr ⟨p, w.elimStr⟩ s = ⟨Name.str p s d, w⟩) ▸
str _ _ (rec _)
| .num p v d =>
(match w with | .num w rfl => rfl : mkNum ⟨p, w.elimNum⟩ v = ⟨Name.num p v d, w⟩) ▸
num _ _ (rec _)
open Syntax in
protected def quote (n : WfName) : Syntax := by
induction n using rec with
| anonymous => exact mkCIdent ``anonymous
| str _p s ih => exact mkCApp ``mkStr #[ih, quote s]
| num _p v ih => exact mkCApp ``mkNum #[ih, quote v]
Mac (Jun 21 2022 at 00:43):
Mario Carneiro said:
One other thing you could do is provide an induction principle that does the pattern match on
p
and patches things up [...]
Cool! I see, though, that term-mode / match
sadly does not support using
. :(
Mac (Jun 21 2022 at 00:45):
Mario Carneiro said:
(I removed the
Wff
suffix, since the convention is to use the namespace for disambiguation here)
I patterned my style off the WellFormed
for RBNode
which uses the Wff
suffix. So are there conflicting conventions or is there some nuance I'm missing?
Mario Carneiro (Jun 21 2022 at 01:18):
I guess that's a conflicting convention, although my impression is that very little work went into that WellFormed
inductive - lean 4 stuff is not very proof optimized right now
Last updated: Dec 20 2023 at 11:08 UTC