I think I need to ask first the value from the reader and then rewrap it with a ReaderT, as in
do
(a, _) <- ask
runReaderT foo a
IMHO, this is not really satisfactory, since it basically forces you to unwrap and rewrap a value. Moreover, as m gets more complex, also the implementation gets more complex, possibly with some lifting and some hoisting.
Is there a better way to approach this?
Is there out there an effect system which has a function similar to
enlarge :: (a -> b) -> Reader a es -> Reader b es
which would allow to have something like (using Bluefin-like syntax here)?
foo :: (e :> es) => Reader a e -> Eff es c
bar :: (e :> es) => Reader (a, b) e -> Eff es c
bar = foo . enlarge fst
that looks nice, but Iâm not able to use it as simply as Iâd like to
foo :: (MonadReader Int m) => m Bool
foo'' :: MonadReader (Int, Char) m => m Bool
foo'' = magnify _1 foo
produces
⢠Couldn't match type: Control.Lens.Zoom.Magnified m0
with: Control.Lens.Zoom.Magnified m
arising from a superclass required to satisfy âMagnify
m0 m Int (Int, Char)â,
arising from a use of âmagnifyâ
NB: âControl.Lens.Zoom.Magnifiedâ is a non-injective type family
The type variable âm0â is ambiguous
Am I forgetting something? m0 can not be m since they have a different environment
I think the functional dependencies on the Magnify class is not enough for GHC to infer Magnify m n Int (Int, Char) from MonadReader (Int, Char) n. If you would rather not provide a concrete monad for foo'', another solution could be to give the following signature to foo'', which should hopefully be resolved once the concrete monad is provided:
foo'' :: Magnify m n Int (Int, Char) => n Bool
foo'' = magnify _1 foo
Though I must admit that neither solution is very satisfying.
I do not think you can go from MonadReader (a,b) m to MonadReader a m in a sane manner. In theory, you could add an instance MonadReader (a,b) m => MonadReader a m, but this new instance would then overlap with all other instances. At the moment, instance resolution is not smart enough for such cases.
I think you can still get close to what you want. You need to run the MonadReader a m effect by relying on MonadReader (a,b) m.
If you use an effect system, you can try something like reinterpret (Control.Monad.Freer)
If you use transformers, you can write something like this:
foo :: (MonadReader Int m) => m Int
foo = ask
bar :: (MonadReader (Int, String) m) => m Int
bar = do
(i, _) â ask
runReaderT foo i
EDIT: Sorry, I have seen just now that this is what you already tried.
For Bluefin specifically, I would try something like this:
data MyReader a e = MyReader (Reader x e) (x -> a)
Then you can implement the enlarge function, though you would also need to add custom run and ask functions.
I think Effectfulâs version is called withReader but the haddocks seem to state itâs deprecated/buggy. mtl has a âwithReaderâ specifically for Reader and ReaderT, but not classy version it seems.
edit: the static interpreter for Reader apparently has a version that not deprecated
withReader assumes that Reader is the first effect in your stack (similarly to what the original withReader assumes).
I would like something like
withReader
:: (HasCallStack, Reader r2 :> es, Reader r1 :> es')
=> (r1 -> r2)
-- ^ The function to modify the environment.
-> Eff es a
-- ^ Computation to run in the modified environment.
-> Eff es' a
but I guess that does not currently exist
P.S. I guess the constraints list should also contain something saying that every other effect in es is contained in es'
withReader in effectful can be slightly generalized to
-- | Execute a computation in a modified environment.
--
-- @since 1.1.0.0
withReader
:: (HasCallStack, Reader r1 :> es)
=> (r1 -> r2)
-- ^ The function to modify the environment.
-> Eff (Reader r2 : es) a
-- ^ Computation to run in the modified environment.
-> Eff es a
withReader f m = do
r <- ask
runReader (f r) m
Yes I did, now fixed, thanks! No need to apologise. It can be very confusing when these things are subtly wrong, so itâs important to get them right and I didnât bother to test it
Are you familiar with the HasX pattern? Instead of
foo :: (MonadReader A m) => m c
bar :: (MonadReader (A, B) m) => m c
You have
class HasA env where
getA :: env -> A
class HasB env where
getB :: env -> B
foo :: (HasA env, MonadReader env m) => m c
bar :: (HasA env, HasB env, MonadReader env m) => m c
Then bar can call foo without any extra ceremony. You can use this pattern with mtl or effects libraries, though the latter may require manual type annotations due to inference woes e.g. in effectful:
baz :: forall env es. (HasA env, HasB env, Reader env :> es) => Eff es ()
baz = do
a <- asks @env getA
...
There is even a package for this, though imo it is somewhat superceded by optics and HasField, which are an alternative to manual HasX classes. The key point is keeping the env in MonadReader env abstract.
The issue I see in such a solution is that bar needs to know about A through the HasA constraint.
That would go away having an instance for HasA everytime we have an instance for HasB and a function B -> A, but I donât that a good approach using typeclasses
I got the idea for something like this but I donât know how practical it is
class (MonadReader r m, forall r' . MonadReader r' (ChangeReader m r')) => SmartMonadReader r m where
data family ChangeReader m r' :: Type -> Type
changeReader :: (r' -> r) -> m a -> ChangeReader m r' a
instance Monad m => SmartMonadReader r (ReaderT r m) where
newtype instance ChangeReader (ReaderT r m) r' a = ChangeReaderReaderT (ReaderT r' m a)
deriving newtype (Functor, Applicative, Monad, MonadReader r')
changeReader f x = ChangeReaderReaderT (withReaderT f x)
instance (SmartMonadReader r m) => SmartMonadReader r (MaybeT m) where
newtype instance ChangeReader (MaybeT m) r' a = ChangeReaderMaybeT (MaybeT (ChangeReader m r') a)
changeReader f x = ChangeReaderMaybeT (mapMaybeT (changeReader f) x)
deriving newtype instance (SmartMonadReader r m) => Functor (ChangeReader (MaybeT m) r')
deriving newtype instance (SmartMonadReader r m) => Applicative (ChangeReader (MaybeT m) r')
deriving newtype instance (SmartMonadReader r m) => Monad (ChangeReader (MaybeT m) r')
deriving newtype instance (SmartMonadReader r m) => MonadReader r' (ChangeReader (MaybeT m) r')
instance (Monoid w, SmartMonadReader r m) => SmartMonadReader r (WriterT w m) where
newtype instance ChangeReader (WriterT w m) r' a = ChangeReaderWriterT (WriterT w (ChangeReader m r') a)
changeReader f x = ChangeReaderWriterT (mapWriterT (changeReader f) x)
deriving newtype instance (Monoid w, SmartMonadReader r m) => Functor (ChangeReader (WriterT w m) r')
deriving newtype instance (Monoid w, SmartMonadReader r m) => Applicative (ChangeReader (WriterT w m) r')
deriving newtype instance (Monoid w, SmartMonadReader r m) => Monad (ChangeReader (WriterT w m) r')
deriving newtype instance (Monoid w, SmartMonadReader r m) => MonadReader r' (ChangeReader (WriterT w m) r')