Zulip Chat Archive
Stream: general
Topic: BaseIO, IO, and monad transformers
James Sully (Jul 28 2024 at 07:54):
I recently refactored my program to use a reader. I've been running into an issue where BaseIO
somehow automatically converts to IO
no problem, but when I wrap them in my transformer, they're not directly compatible.
Simplified:
def CmdHandlerEnv := Unit
abbrev CmdHandlerT (m : Type → Type) : Type → Type := ReaderT CmdHandlerEnv m
def baseIOAction : BaseIO Unit := pure ()
-- ok
example : IO Unit := baseIOAction
def transformedBaseIOAction : CmdHandlerT BaseIO Unit := pure ()
/-
type mismatch
transformedBaseIOAction
has type
CmdHandlerT BaseIO Unit : Type
but is expected to have type
CmdHandlerT IO Unit : Type
-/
example : CmdHandlerT IO Unit := transformedBaseIOAction
I can work around it like this:
instance : Coe (ReaderT ρ BaseIO α) (ReaderT ρ IO α) where
coe act := λ r ↦ (act.run r).toIO
But this feels kind of janky and specific
My questions are
- what's the mechanism by which
BaseIO
is converted toIO
automatically? I can't find aCoe
instance. - Is there a better way to do this?
- could that better way be done generically in the standard library for every transformer?
Edward van de Meent (Jul 28 2024 at 08:13):
if instead of example
you use def foo : IO Unit := baseIOAction
, you can use #print
to discover what's going on in the conversion:
def CmdHandlerEnv := Unit
abbrev CmdHandlerT (m : Type → Type) : Type → Type := ReaderT CmdHandlerEnv m
def baseIOAction : BaseIO Unit := pure ()
-- ok
def foo : IO Unit := baseIOAction
#print foo -- def foo : IO Unit := @liftM BaseIO (EIO IO.Error) (instMonadLiftTOfMonadLift BaseIO BaseIO (EIO IO.Error)) Unit baseIOAction
--
basically, it seems like there is a Coe
-like typeclass MonadLift
saying you can lift BaseIO
to IO
.
you can check this by using #synth MonadLift BaseIO IO
Edward van de Meent (Jul 28 2024 at 08:15):
it seems to go through an intermediate class MonadLiftT
but that looks to me like an implementation detail
James Sully (Jul 28 2024 at 08:17):
Thanks, I'll play around with this
James Sully (Jul 28 2024 at 08:26):
Ok I fumbled around in the dark a bit and got stuck:
instance {m n ω : Type → Type} {t : (Type → Type) → Type → Type} [MonadLift m n] [MonadLift ω (t ω)] : MonadLift (t m) (t n) := sorry
"cannot find synthesization order for instance @instMonadLift with type
{m n ω : Type → Type} →
{t : (Type → Type) → Type → Type} → [inst : MonadLift m n] → [inst : MonadLift ω (t ω)] → MonadLift (t m) (t n)
all remaining arguments have metavariables:
MonadLift ?ω (t ?ω)"
James Sully (Jul 28 2024 at 08:28):
I'm not really sure whether I have the right idea here or not
Yaël Dillies (Jul 28 2024 at 08:29):
Should {ω : Type → Type} [MonadLift ω (t ω)]
instead be [∀ ω, MonadLift ω (t ω)]
?
James Sully (Jul 28 2024 at 08:30):
Yes!
James Sully (Jul 28 2024 at 08:30):
instance {m n: Type → Type} {t : (Type → Type) → Type → Type} [MonadLift m n] [∀ ω, MonadLift ω (t ω)] : MonadLift (t m) (t n) where
monadLift {α : Type} (action : (t m) α) : (t n) α :=
sorry
James Sully (Jul 28 2024 at 08:30):
the next thing to figure out is how to implement it
James Sully (Jul 29 2024 at 14:04):
I haven't managed to figure it out in full generality (not even sure whether it's possible), but here's one for ReaderT
instance monadLiftReaderT [MonadLift m n] : MonadLift (ReaderT σ m) (ReaderT σ n) where
monadLift action := λ r => liftM <| action.run r
Would this be a good addition to the standard library?
Sebastian Ullrich (Jul 30 2024 at 12:03):
I think such instances could be problematic when you stack the same transformer multiple times as now you get quadratically many possible instances
James Sully (Jul 30 2024 at 15:52):
It definitely feels like there's an ergonomics issue here, but I'm not sure what the right solution is
James Sully (Jul 30 2024 at 15:55):
what about just
instance : MonadLift (ReaderT σ BaseIO) (ReaderT σ IO) where
monadLift action := λ r => liftM <| action.run r
Sebastian Ullrich (Jul 30 2024 at 15:57):
Feels quite ad-hoc :) . It's a fine local instance though.
James Sully (Jul 30 2024 at 15:58):
I agree it's ad hoc, but in the absence of a general solution, it feels like an 80/20 thing that would add a lot of convenience. Defining just a ReaderT σ IO
is pretty common (at least in haskell).
Last updated: May 02 2025 at 03:31 UTC