To me local
“makes sense”, though maybe I’m stuck in stockholm. My reasoning:
-
The monomorphic version of
local
is merely modifying an ordinary (locally scoped) function parameter, so no problems here:local :: (r -> r) -> ReaderT r m a -> ReaderT r m a local f (ReaderT onEnv) = ReaderT $ \env -> onEnv (f env)
-
Similarly, the polymorphic
mtl
version is pretty routine since it’s just dictionary passing:local :: MonadReader r m => (r -> r) -> m a -> m a
So my vague impression is that the semantics for effectful
, etc. should be pretty sensible too, since they’re morally equivalent to typeclasses, right?
I don’t use local
often, but I have used it for namespacing logs. For instance:
addNamespace :: MonadReader [String] m => String -> m a -> m a
addNamespace ns = local (++ [ns])
foo :: MonadReader [String] m => m ()
foo = addNamespace "foo" $ do
log "something"
addNamespace "doThing" $ log "more logs"
-- [foo]: something
-- [foo.doThing]: more logs
IMO the dynamic dispatch is muddying the waters with local
specifically, since I’m not sure why you’d ever want different behavior. In other words, this static version in bluefin
seems useful and sensible:
local ::
forall r e es a.
(e :> es) =>
Reader r e ->
(r -> r) ->
(forall e1. Reader r e1 -> Eff (e1 :& es) a) ->
Eff es a
local (MkReader ns) f = runReader (f ns)
addNamespace ::
forall e es a.
(e :> es) =>
Reader [String] e ->
String ->
(forall e1. Reader [String] e1 -> Eff (e1 :& es) a) ->
Eff es a
addNamespace r ns = local r (++ [ns])
foo ::
forall e1 e2 es.
( e1 :> es,
e2 :> es
) =>
IOE e1 ->
Reader [String] e2 ->
Eff es ()
foo io rdr = addNamespace rdr "foo" $ \rdr2 -> do
log io rdr2 "something"
addNamespace rdr2 "doThing" $ \rdr3 -> log io rdr3 "more logs"
I implemented it here.
That said, effectful
and friends do let you supply genuine dynamic behavior for Local
and Ask
. I don’t see why this would be fundamentally impossible with bluefin
(fleeting attempt here), but I’ve yet to figure it out.