Zulip Chat Archive
Stream: new members
Topic: concise enum-with-values type
Daniel Windham (Apr 12 2024 at 06:04):
@Graham Leach-Krouse and I are working on a binary file parser and there are lots of magic numbers. To stay organized, we parse these numbers into named enum values, and later serialize the enum values back to Nats. Modern software languages usually provide a concise way to define enums with values since it's a common pattern, but we're not sure how to write this concisely in Lean.
Right now we implement this with an inductive enum type, plus two separate functions toNat and fromNat, plus a pair of theorems that toNat $ fromNat
and fromNat $ toNat
are identities. It's a ton of boilerplate, and we have hundreds (or maybe thousands!) of magic numbers to deal with.
Instead, we'd like to write something like
inductive_with_values Weekday (_: Fin 7) where
| sunday : Weekday := 0
| monday : Weekday := 1
| ...
#eval Weekday.sunday.value -- 0
#eval Weekday.mk 0 -- Weekday.sunday
#check Weekday.value_of_mk -- Weekday.value_of_mk (n: Nat) : (Weekday.mk n).value = n
#check Weekday.mk_of_value -- Weekday.mk_of_value (w: Weekday) : Weekday.mk w.value = w
Also, in some cases, we need to map large number ranges to a common enum label and preserve the original value, e.g.:
inductive_with_values Vehicle (n: Nat) where
| Volvo : Vehicle := 0
| Hyundai : Vehicle := 1
| ...
| Tesla : Vehicle := 34
| UnusedCar : Vehicle := n if 34 < n && n < 512
| Boeing : Vehicle := 512
| ...
| Airbus : Vehicle := 528
| UnusedPlane : Vehicle := n if 528 < n && n < 1024
| Invalid : Vehicle := n -- all remaining numbers
#eval Vehicle.mk 36 -- Vehicle.UnusedCar 36
#eval (Vehicle.mk 36).value -- 36
Does something like this exist already? If not, any suggestions on how we can add it? Neither of us has written any metaprogramming yet. Thanks!
Eric Wieser (Apr 12 2024 at 07:51):
If your enum values are specifically Fin n
, then FinEnum
might work for you
Eric Wieser (Apr 12 2024 at 07:51):
Though it's a rather unloved bit of API that needs some work
Marcus Rossel (Apr 12 2024 at 08:00):
Here's a makeshift enum
macro that covers the basic case:
import Lean
open Lean Parser Command
declare_syntax_cat enum_case
syntax "| " ident " := " term : enum_case
private def split (cases : TSyntaxArray `enum_case) : (Array Ident) × (Array Term) :=
cases.foldl (init := (#[], #[])) fun (names, values) => fun
| `(enum_case| | $name := $value) => (names.push name, values.push value)
| _ => unreachable!
macro "enum" e:ident " : " ty:term (" where")? cases:enum_case* d:optDeriving : command => do
let (names, values) := split cases
let eIdent (suffix : Name) := mkIdentFrom e (e.getId ++ suffix)
`(
inductive $e:ident $[| $names:ident]* $d:optDeriving
def $(eIdent `val) : $e → $ty $[| $names:ident => $values]*
def $(eIdent `ofVal) : $ty → Option $e $[| $values => some $names]* | _ => none
theorem $(eIdent `ofVal_val) (e : $e) : $(eIdent `ofVal) ($(eIdent `val) e) = e := by cases e <;> rfl
theorem $(eIdent `val_ofVal) {n : $ty} {e : $e} (h : $(eIdent `ofVal) n = some e) : $(eIdent `val) e = n := by
unfold $(eIdent `ofVal) at h
split at h <;> injection h
all_goals rename_i h; rw [←h]; rfl
)
enum E : Nat where
| one := 1
| two := 2
deriving Inhabited, BEq
#print E
#print E.val
#print E.ofVal
#check E.ofVal_val
#check E.val_ofVal
Or do you always know that your enum cases cover all possible values of the given type?
Daniel Windham (Apr 17 2024 at 14:48):
Thanks @Marcus Rossel , this is very helpful! (Sorry for my very delayed reply here.)
I'm trying to generalize this example to retain the original Nat value in the default case. I need to know that this retained value is a Nat that does not match any of the enumerated cases.
I'm getting stuck trying to dynamically construct an array syntax term from the user-provided data. How can I construct a TSyntax term like [1, 2]
given an array of terms or an array of Nats?
Here's a simplified example:
macro "test" e:ident cases:enum_case* : command => do
let (names, values) := splitArray cases -- values has type Array Term (same as previous post)
let valuesHardcoded := [1, 2]
`(
-- def $e:ident := $values -- fails, because rhs is expected to have type TSyntax `term
-- def $e:ident := $valuesHardcoded -- fails, because rhs is expected to have type TSyntax `term
def $e:ident := [1, 2]
)
test D
| one := 1
| two := 2
#print D -- def D : List Nat := [1, 2]
If it's useful, here is what I'm trying to achieve overall:
-- supporting definition
structure NatOtherThan (excluded : List Nat) where
val : Nat
isOther : ¬excluded.contains val
deriving BEq, Repr
example : NatOtherThan [1, 3, 4] := ⟨2, by intro h; cases h⟩
-- the user writes this:
enum EE (n: Nat) where
| one := 1
| three := 3
| other := n
-- `enum` is desugared into this inductive type (plus supporting functions)
inductive EE
| one
| three
| rest (n : NatOtherThan [1, 3])
deriving Inhabited, BEq, Repr
example := (EE.rest ⟨2, by intro h; cases h⟩)
Graham Leach-Krouse (Apr 17 2024 at 14:53):
I played around with this a bit, and came up with the following, which should add a default "unknown" case and also allow docComments on the constructors. It's my first nontrivial macro though, so I'm guessing that it can be improved quite a bit...
import Lean
open Lean Parser Command
declare_syntax_cat enum_case
syntax (docComment)? " | " ident " := " term : enum_case
private def split (cases : TSyntaxArray `enum_case) : (Array Ident) × (Array Term) × (Array (Lean.TSyntax `Lean.Parser.Command.docComment)) :=
cases.foldl (init := (#[], #[], #[])) fun (names, values, comments) => fun
| `(enum_case| | $name := $value) => (names.push name, values.push value, comments.push emptyComment)
| `(enum_case| $comment:docComment | $name := $value) => (names.push name, values.push value, comments.push comment)
| _ => unreachable!
where commentLeader : Lean.Syntax := Lean.Syntax.atom SourceInfo.none "/--"
commentBody : Lean.Syntax := Lean.Syntax.atom SourceInfo.none "undocumented -/"
emptyComment : Lean.TSyntax `Lean.Parser.Command.docComment := Lean.TSyntax.mk $
Lean.Syntax.node SourceInfo.none `Lean.Parser.Command.docComment #[commentLeader, commentBody]
macro "enum" e:ident " : " ty:term (" where")? cases:enum_case* d:optDeriving : command => do
let (names, values, comments) := split cases
let eIdent (suffix : Name) := mkIdentFrom e (e.getId ++ suffix)
`(
inductive $e:ident $[$comments:docComment | $names:ident]* | unknown (x:$ty) $d:optDeriving
def $(eIdent `val) : $e → $ty $[| $names:ident => $values]* | unknown x => x
def $(eIdent `ofVal) : $ty → $e $[| $values => $names]* | x => unknown x
instance : Coe $e $ty where coe := $(eIdent `val)
/- theorem $(eIdent `ofVal_val) (e : $e) : $(eIdent `ofVal) ($(eIdent `val) e) = e := by cases e <;> try rfl <;> unfold $(eIdent `val) -/
theorem $(eIdent `val_ofVal) {n : $ty} {e : $e} (h : $(eIdent `ofVal) n = some e) : $(eIdent `val) e = n := by
unfold $(eIdent `ofVal) at h
split at h <;> injection h
all_goals rename_i h; rw [←h]; rfl
)
Marcus Rossel (Apr 17 2024 at 15:02):
Daniel Windham said:
I'm getting stuck trying to dynamically construct an array syntax term from the user-provided data. How can I construct a TSyntax term like
[1, 2]
given an array of terms or an array of Nats?
What you're looking for is docs#Lean.Quote:
macro "one_two" : term => do
let vals := [1, 2]
let stx := Lean.quote vals
`($stx)
#eval one_two -- [1, 2]
Daniel Windham (Apr 17 2024 at 15:05):
Perfect, thanks @Marcus Rossel.
Marcus Rossel (Apr 17 2024 at 15:33):
@Daniel Windham Here's an outline of a macro that follows the example you gave:
Macro
Notably, I've removed the proofs, because I'm too lazy to figure them out. But I bet that's something you'll figure out.
Also, there's a sorry
which should be a proof that the value appearing in other
is not one of the other constructors' values. I'm not sure how to get that info in a match statement. I was kind of expecting the hypothesis h
to contain that info.
Eric Wieser (Apr 17 2024 at 15:33):
Often docs#Lean.toExpr is a better choice than Lean.quote
Eric Wieser (Apr 17 2024 at 15:34):
(with docs#Expr.toSyntax)
Marcus Rossel (Apr 17 2024 at 15:35):
Eric Wieser said:
Often docs#Lean.toExpr is a better choice than
Lean.quote
Why is that?
Eric Wieser (Apr 17 2024 at 15:36):
There are more docs#Lean.ToExpr instances than docs#Lean.Quote
Daniel Windham (Apr 17 2024 at 15:45):
Thanks @Marcus Rossel and @Eric Wieser.
@Marcus Rossel, I also find it odd that the match statement doesn't have a hypothesis about n
not being a previous value. Perhaps there's a more verbose form of match that captures this. Either way, here's what I've been doing instead. It's a bit more annoying for downstream proofs but it seems fine.
def E.ofValX : Nat → E2 := fun n =>
if h : [1, 2].contains n then
match n with
| 1 => E.one
| 2 => E.two
else
E.other ⟨n, h⟩
Marcus Rossel (Apr 17 2024 at 15:52):
@Daniel Windham Nice! Here's an updated version of the macro then:
Macro
Daniel Windham (Apr 17 2024 at 15:58):
:thumbs_up:
Daniel Windham (Apr 21 2024 at 16:43):
For the basic case of enums, my macro works great.
But I'm struggling to write a more general macro where each constructor in the generated inductive type declaration is specified in one of two different patterns depending on its input data.
Here's an example:
enum MyEnum (n: Nat)
| one => 1
| small => n, 1 < n < 5
| five => 5
| big => n, 5 < n
should produce:
inductive MyEnum
| one
| small (n: Nat) (h: 1 < n < 5)
| five
| big (n: Nat) (h: 5 < n)
If none of the constructors had arguments, this would just be
`(inductive $e:ident $[| $names:ident]*)
If all of the constructors had arguments, this would be
`(inductive $e:ident $[| $names:ident (n : $ty) (h: $conditions)]*)
How can I generalize this to allow a mix of constructors with and without arguments, in any order?
Marcus Rossel (Apr 22 2024 at 18:16):
I think what I would do is to write the constructors in the form | name : α → β → ... → MyEnum
. Then whenever there are no parameters you just omit the α → β → ... →
part.
Daniel Windham (Apr 22 2024 at 19:41):
Hmm - I think I'm missing something obvious here. @Marcus Rossel that approach seems reasonable, but I don't know how to implement it.
Specifically, I can't figure out how to do either of the following:
(a) construct a TSyntax `term
from a type. For instance, let t : TSyntax `term := Lean.quote Nat
fails
(b) inline conditional logic within a `(...)
expression, e.g. something like `(... | $names : $(if $params then `($params → ) ) MyEnum
For (a), is there a way to construct a syntax term with `(...)
? Perhaps something like this?
let t: MacroM (TSyntax `term) := `(...)
let u : TSyntax `term := someFn t
Daniel Windham (Apr 22 2024 at 21:29):
Ah - let u : TSyntax `term <- `(...)
does the trick.
If there's a way to do inline conditional logic within a `(...)
expression, that would still be good to know.
Thanks!
Eric Wieser (Apr 23 2024 at 07:51):
You can, ← if x then `(foo) else `(bar)
Daniel Windham (Apr 23 2024 at 12:05):
@Eric Wieser , that's an example of inlining `(...)
expressions within a condition. Is there a way to inline conditions within a `(...)
expression? Otherwise for a large `(...)
expression, all conditional logic needs to be constructed out of context in a bunch of prior variables, which makes for messy code.
Marcus Rossel (Apr 23 2024 at 12:14):
If you have an optional syntax object o
, then its antiquotation syntax is $[$o]?
. This is similar to how $[...]*
is used for arrays/lists of syntax. I don't think there's anything specific for conditional expressions.
Marcus Rossel (Apr 23 2024 at 12:20):
What I was suggesting earlier was therefore to basically do all of the syntax computation beforehand. Given the enum entries, construct the lists names
and ctorTypes
where ctorTypes[i]
is MyEnum
when the case has no range and (n : Nat) -> (_ < _ < _) -> MyEnum
when it does have a range. Then you can just use the regular syntax:
`(inductive $e:ident $[| $names:ident : $ctorTypes]*)
Daniel Windham (Apr 23 2024 at 12:23):
@Marcus Rossel thank you, I have that working now! Too bad about having to do all the syntax computation beforehand, but at least I can do what I need =]
Marcus Rossel (Apr 23 2024 at 12:25):
Would you mind posting your complete macro here? I'd be interested to see how it works :)
Daniel Windham (Apr 23 2024 at 12:25):
Sure - I haven't put the full pieces together yet but I will post here and tag you when I do!
Daniel Windham (Apr 23 2024 at 12:30):
Out of curiosity, what is the syntax type for a constructor case within an inductive declaration? Is this something Lean supports?
I could keep the code more organized if I was able to do something like:
def mkConstructor (...) : TSyntax `constructor :=
if (...) then
<- `(constructor| | $name:ident)`
else
<- `(constructor| | $name:ident (n: Nat) -> (h: _ < n < _))`
let constructors := data.map mkConstructor
`(inductive $e:ident $[constructors]*)
Marcus Rossel (Apr 23 2024 at 13:28):
Here's the complete syntax for inductive types: https://github.com/leanprover/lean4/blob/6fce8f7d5cd18a4419bca7fd51780c71c9b1cc5a/src/Lean/Parser/Command.lean#L161
So you either want ctor
or optDeclSig
.
Daniel Windham (Apr 23 2024 at 13:39):
Ah perfect, thanks! I saw that before and couldn't get it working. This works though:
let x := ← `(ctor| | one (n: Nat) (h: 2 ≤ n ∧ n < 4) : $e:ident)
let y := ← `(inductive $e:ident where $x)
Graham Leach-Krouse (Apr 23 2024 at 13:45):
For conditional splices/antiquotations, something like this seems to work:
let cond ← `(command| def x:= $(← if true then `(1) else `(2)))
Daniel Windham (Apr 23 2024 at 14:30):
Nice!
Marcus Rossel (Apr 23 2024 at 15:59):
I think that's exactly what @Eric Wieser was suggesting :sweat_smile:
Daniel Windham (Apr 23 2024 at 16:01):
Ah oops! The $(...)
syntax was new to me
Daniel Windham (May 08 2024 at 13:28):
@Marcus Rossel / others, I finally got more time to work on this. Thanks again for all your help! Here's the result, starting with examples:
-- can mix literal, guarded, and match-all values. comments are optional
enum E (n: Nat)
/-- one -/
| one => 1
| small => n, 2 ≤ n ∧ n < 4
/-- four -/
| four => 4
/-- other -/
| other => n
#eval E.four
#eval E.ofVal 4
#eval E.small 2 (by decide)
#eval (E.other 5 (by decide)).val
#eval (E.ofVal 5).val
-- proves round-trip identities for both ofVal ⬝ val and val ⬝ ofVal
#check E.ofVal_val
#check E.val_ofVal
-- works with types other than Nat
enum FinEnum (n: Fin 3)
| zero => 0
| one => 1
| two => 2
#eval FinEnum.zero.val
#eval FinEnum.ofVal 0
-- adds a default case if it isn't given explicitly
enum ImplicitDefault (n: Nat)
| one => 1
| two => 2
#eval ImplicitDefault.default 5 (by decide)
#eval ImplicitDefault.ofVal 5
Here's (approximately) what the macro generates for the first example:
inductive E
| one
| small (n : Nat) (h: (2 ≤ n ∧ n < 4) ∧ ¬(n = 1))
| four
| other (n : Nat) (h: ¬(n = 1) ∧ ¬(2 ≤ n ∧ n < 4) ∧ ¬ (n = 4))
deriving BEq, Inhabited
instance : Repr E where
reprPrec := fun e _ => match e with
| .one => "one"
| .small n _ => s!"small {n}"
| .four => "four"
| .other n _ => s!"other {n}"
def E.val : E → Nat
| E.one => 1
| E.small n _ => n
| E.four => 4
| E.other n _ => n
def E.ofVal : Nat → E := fun n =>
if _ : n = 1 then .one else
if _ : 2 ≤ n ∧ n < 4 then .small n (by omega) else
if _ : n = 4 then .four else
if _ : true then .other n (by omega) else
unreachable!
theorem E.ofVal_val (e : E) : E.ofVal (E.val e) = e := by
cases e <;> try rfl
all_goals rename _ => h; simp [E.val, E.ofVal, h]
theorem E.val_ofVal (n : Nat) : E.val (E.ofVal n) = n := by
unfold E.ofVal
repeat split; simp only [*, E.val]
simp only [*, E.val]
contradiction
And finally, here's the macro:
import Lean
open Lean Parser Command Syntax
declare_syntax_cat enum_case
syntax atomic((docComment)? " | " ident " => " ident ", " term) : enum_case
syntax (docComment)? " | " ident " => " term : enum_case
private inductive EnumCaseValue
| literalValue (value : Term)
| parametricValue (value : Ident) (cond : Term)
deriving Inhabited
private structure EnumCaseParsed where
name : Ident
value : EnumCaseValue
comment : Lean.TSyntax `Lean.Parser.Command.docComment
deriving Inhabited
private def makeComment (comment: String) : Lean.TSyntax `Lean.Parser.Command.docComment :=
let commentLeader : Lean.Syntax := Lean.Syntax.atom SourceInfo.none "/--"
let commentBody : Lean.Syntax := Lean.Syntax.atom SourceInfo.none s!"{comment} -/"
Lean.TSyntax.mk $ Lean.Syntax.node SourceInfo.none `Lean.Parser.Command.docComment #[commentLeader, commentBody]
private def parseCase (case : TSyntax `enum_case) : EnumCaseParsed :=
match case with
| `(enum_case| | $name => $value) => ⟨name, .literalValue value, emptyComment⟩
| `(enum_case| | $name => $value, $cond) => ⟨name, .parametricValue value cond, emptyComment⟩
| `(enum_case| $comment:docComment | $name => $value) => ⟨name, .literalValue value, comment⟩
| `(enum_case| $comment:docComment | $name => $value, $cond) => ⟨name, .parametricValue value cond, comment⟩
| _ => unreachable!
where emptyComment := makeComment "undocumented"
private def parseCases (cases : TSyntaxArray `enum_case) : Array EnumCaseParsed :=
cases.foldl (init := (#[])) fun (parsedCases) => fun (case) =>
parsedCases.push (parseCase case)
-- TODO: prove termination.
-- Warning: this is not performant if there are many name collisions.
private partial def getUniqueName (base : String) (names : List String) : String :=
let rec loop (i : Nat) : String :=
let name := base ++ (if i == 0 then "" else s!"_{i}")
if name ∈ names then loop (i + 1) else name
loop 0
private def addCaseDefaults (n: Ident) (cases : Array EnumCaseParsed) : MacroM (Array EnumCaseParsed) := do
-- Allow the final case to omit the true condition.
let ⟨name, value, comment⟩ := cases.back
let trueValue ← `(true)
let defaultValue := EnumCaseValue.parametricValue n trueValue
let (cases, lastValue) := match value with
| .literalValue v =>
if v == n then (cases.pop.push ⟨name, defaultValue, comment⟩, defaultValue)
else (cases, value)
| .parametricValue _ _ => (cases, value)
-- Append a default case if the final case is not a catch-all.
let names := cases.map (·.name.getId.toString)
let addDefaultCase (n : Ident) (cases : Array EnumCaseParsed) : Array EnumCaseParsed :=
let defaultName: Ident := mkIdentFrom n (getUniqueName "default" names.toList)
cases.push ⟨defaultName, defaultValue, makeComment "default case"⟩
let cases := match lastValue with
| .literalValue _ => addDefaultCase n cases
| .parametricValue _ h => if h == trueValue then cases else addDefaultCase n cases
pure cases
private def validateCases (n: Ident) (cases : Array EnumCaseParsed) : MacroM Unit :=
-- validate that parametric values use the same name as the enum value
for case in cases do
match case.value with
| .literalValue _ => pure ()
| .parametricValue v _ => if v != n then
Macro.throwErrorAt v s!"parametric value {v.getId.toString} must match the enum parameter {n.getId.toString}"
/-
Make the constructor types for the inductive type that an enum desugars to.
Constructors for non-literal cases bind to a value and a proof that the value is
in the case's range. A case's range is limited by both its local condition and
the conditions of all previous cases (since cases are matched greedily). The net
restriction on a case is the conjunction of its local restriction and the
negations of all prior restrictions. A literal constructor's implied restriction
is that of equality with its literal value.
To make these constructor types, we fold over the cases, building the
constructor type for each case and the net restriction after that case.
-/
private def mkCtorTypes (e n : Ident) (ty : Term) (parsedCases : Array EnumCaseParsed) : MacroM (Array Term) := do
let mkCtorTypeAndCarriedRestrictions (value : EnumCaseValue) (priorRestrictions : Term) : MacroM (Term × Term) := do
let thisType ← match value with
| .literalValue _ => `($e)
| .parametricValue _ h => `(($n:ident: $ty) → (h: $h ∧ $priorRestrictions) → $e)
let netRestrictions ← match value with
| .literalValue v => `(¬($n:ident = $v) ∧ $priorRestrictions)
| .parametricValue _ h => `(¬($h) ∧ $priorRestrictions)
pure ⟨thisType, netRestrictions⟩
let ⟨ctorTypes, _⟩ ← parsedCases.foldlM (init := (#[], ←`(true))) fun (valuesAcc, restrictions) (case) => do
let (thisType, netRestrictions) ← mkCtorTypeAndCarriedRestrictions case.value restrictions
return (valuesAcc.push thisType, netRestrictions)
pure ctorTypes
/--
The `enum` macro defines an enumeration type over a base type. The enumeration
type defines cases that partition the base type. Each case is associated with a
literal value or a guard condition on values. Cases are matched greedily.
An enum type defines constructors for each of its cases. Constructors for
guarded types take a value and a proof that the value is in the case's range. An
enum type also defines functions to convert between the enum type and values of
the base type, and defines proofs that a round-trip in either direction between
a value of the base type and its corresponding enum value is the identity.
-/
macro "enum" e:ident "(" n:ident " : " ty:term ")" (" where")? cases:enum_case* : command => do
-- Must have at least one case. Don't enforce this in the syntax parser since
-- it's easier to give a good error message here.
if cases.size == 0 then Macro.throwErrorAt e "enum must have at least one case"
let eIdent (suffix : Name) := mkIdentFrom e (e.getId ++ suffix)
let toStrLit (n : Name) := Lean.Syntax.mkStrLit n.toString
let rawParsedCases := parseCases cases
let parsedCases ← addCaseDefaults n rawParsedCases
let _ ← validateCases n parsedCases
let comments := parsedCases.map (·.comment)
let names := parsedCases.map (·.name)
let ctorTypes ← mkCtorTypes e n ty parsedCases
let reprCases ← parsedCases.mapM fun ⟨name, value, _⟩ => do match value with
| .literalValue _ => `(Term.matchAltExpr| | .$name:ident => $(toStrLit name.getId))
| .parametricValue v _ => `(Term.matchAltExpr| | .$name:ident $v _ => s!"{$(toStrLit name.getId)} {$v}")
let valCases ← parsedCases.mapM fun ⟨name, value, _⟩ => do match value with
| .literalValue v => `(Term.matchAltExpr| | .$name:ident => $v)
| .parametricValue v _ => `(Term.matchAltExpr| | .$name:ident $v _ => $v)
let ofValCases: Term ← parsedCases.reverse.foldlM (init := ←`(unreachable!)) fun res case => do
let ⟨name, value, _⟩ := case
match value with
| .literalValue v => `(if _ : $n = $v then .$name:ident else $res)
| .parametricValue _ h => `(if _ : $h then .$name:ident $n (by simp; omega) else $res)
`(
inductive $e:ident
$[$comments:docComment | $names:ident : $ctorTypes]*
deriving Inhabited, BEq
instance : Repr $e where reprPrec := fun e _ => match e with $reprCases:matchAlt*
def $(eIdent `val) : $e → $ty $valCases:matchAlt*
def $(eIdent `ofVal) : $ty → $e := fun $n => $ofValCases
theorem $(eIdent `ofVal_val) (e : $e) : $(eIdent `ofVal) ($(eIdent `val) e) = e := by
cases e <;> try rfl
all_goals rename _ => h; simp [$(eIdent `val): ident, $(eIdent `ofVal): ident, h]
theorem $(eIdent `val_ofVal) (n : $ty) : $(eIdent `val) ($(eIdent `ofVal) n) = n := by
unfold $(eIdent `ofVal)
repeat split; simp only [*, $(eIdent `val): ident]
simp only [*, $(eIdent `val): ident]
contradiction
)
Last updated: May 02 2025 at 03:31 UTC