Zulip Chat Archive
Stream: new members
Topic: Cleaner coercion
Garrison Venn (Oct 20 2025 at 17:34):
In my solution to exercise 4.3.3.4.3. A Tracing Evaluator in the book Functional Programming in Lean.
I used an explicit manual coercion in the function
applyTraced
. This coercion transformed type GPrim Empty to GPrim (ToTrace (GPrim Empty)) with the following:
let nop : GPrim (ToTrace (GPrim Empty)) :=
match op with
| .plus => .plus
| .minus => .minus
| .times => .times
Beyond not fully understanding why the coercion would not be automatic, I was wonder if there was cleaner syntax for this.
Thanks in advance
Code:
inductive GExpr (op : Type) where
| const : Int → GExpr op
| prim : op → GExpr op → GExpr op → GExpr op
inductive GPrim (special : Type) where
| plus
| minus
| times
| other: special → GPrim special
def applyPrim [Monad m]
(applySpecial : special → Int → Int → m Int) :
GPrim special → Int → Int → m Int
| GPrim.plus, x, y => pure (x + y)
| GPrim.minus, x, y => pure (x - y)
| GPrim.times, x, y => pure (x * y)
| GPrim.other op, x, y => applySpecial op x y
def evaluateM [Monad m]
(applySpecial : special → Int → Int → m Int) :
GExpr (GPrim special) → m Int
| GExpr.const i => pure I
| GExpr.prim p e1 e2 =>
evaluateM applySpecial e1 >>= fun v1 =>
evaluateM applySpecial e2 >>= fun v2 =>
applyPrim applySpecial p v1 v2
structure WithLog (logged : Type) (α : Type) where
log : List logged
val : α
deriving Repr
abbrev GLogged : Type := (GPrim Empty × Int × Int)
instance : Monad (WithLog GLogged) where
pure x := ⟨[], x⟩
bind
| ⟨ll, val⟩, next =>
match next val with
-- order in order of appearance
| ⟨nl, v⟩ => ⟨ll ++ nl, v⟩
inductive ToTrace (α : Type) : Type where
| trace : α → ToTrace α
deriving instance Repr for Empty
deriving instance Repr for GPrim
abbrev GTrace : Type :=
ToTrace (GPrim Empty) → Int → Int → WithLog GLogged Int
def emptyTraced : GTrace := fun _ _ _ => pure 0
def applyTraced : GTrace
| .trace op, lhs, rhs =>
-- Is there a way to use coercion for this
let nop : GPrim (ToTrace (GPrim Empty)) :=
match op with
| .plus => .plus
| .minus => .minus
| .times => .times
applyPrim (emptyTraced) nop lhs rhs >>= fun res =>
⟨[(op, lhs, rhs)], res⟩
Eric Wieser (Oct 20 2025 at 17:44):
I recommend defining
def GPrim.map {α β : Type} (f : α → β) : GPrim α → GPrim β
| .plus => .plus
| .minus => .minus
| .times => .times
| .other a => .other (f a)
Eric Wieser (Oct 20 2025 at 17:45):
Then nop is op.map nofun
Garrison Venn (Oct 21 2025 at 13:07):
Thanks!
Just to get used to Functors, I went with your example translated to:
instance : Functor GPrim where
map f
| .plus => .plus
| .minus => .minus
| .times => .times
| .other a => .other (f a)
def applyTraced : GTrace
| .trace op, lhs, rhs =>
let nop : GPrim (ToTrace (GPrim Empty)) := nofun <$> op
applyPrim (emptyTraced) nop lhs rhs >>= fun res =>
⟨[(op, lhs, rhs)], res⟩
However I don't really understand the use of nofun it this case. I view it like nomatch where it cannot be used. Though it is not being used in Functor.map above, in the applyTraced implementation, it is allowed to be "captured" in map's first argument. So that is throwing me off with my current lack luster intuition. Also it seems nofun can be "cast" to any single argument function type involving Empty (I guess). Any explanation or reference would be useful. I may not currently understand the given explanation, but I can save it for later perusal.
Thanks again
Eric Wieser (Oct 21 2025 at 13:29):
nofun is shorthand for fun x => nomatch x
Last updated: Dec 20 2025 at 21:32 UTC