Reader
monad and Env
comonad
It is said that Env comonad is identical to Reader monad. Then how can you write the same code using Reader
and Env
?
Let’s take a very simple example that uses Reader
.
import Control.Monad
( (>>=)
, (>=>)
, return
)
import Control.Monad.Reader
( Reader
, ask
, runReader
)
add :: Float -> Reader Int Float
add n = ask >>= \e -> return $ n + fromIntegral e
mul :: Float -> Reader Int Float
mul n = ask >>= \e -> return $ n * fromIntegral e
result, result' :: Float
result = runReader (add 2 >>= mul) 3
result' = runReader ((add >=> mul) 2) 3
How will it look when you write the same code using Env
?
import Control.Comonad
( (=>>)
, (=>=)
, extract
)
import Control.Comonad.Env
( Env
, ask
, env
)
add :: Env Int Float -> Float
add env = fromIntegral (ask env) + extract env
mul :: Env Int Float -> Float
mul env = fromIntegral (ask env) * extract env
result, result' :: Float
result = extract $ env 3 2 =>> add =>> mul
result' = (add =>= mul) $ env 3 2
When you use Reader
, each function (add
and mul
) returns a Reader
to lift a value into Reader
monad. The bind operator (>>=
) passes an environment from a functon to another function.
On the other hand, with Env
, each function takes an Env
and extract a value from Env
comonad. The extend operator (=>>
) passes an environment from a functio nto another function.
It’ll help understand what’s going on under the hood by implementing Reader
and Env
by yourself.
{-# LANGUAGE InstanceSigs #-}
import Control.Monad ((>=>))
newtype Reader e a = Reader { runReader :: e -> a }
instance Functor (Reader e) where
fmap :: (a -> b) -> Reader e a -> Reader e b
fmap f (Reader g) = Reader $ f . g
instance Applicative (Reader e) where
pure x = Reader $ \e -> x
() :: Reader e (a -> b) -> Reader e a -> Reader e b
Reader f Reader x = Reader $ \e -> f e (x e)
instance Monad (Reader e) where
(>>=) :: Reader e a -> (a -> Reader e b) -> Reader e b
Reader x >>= f = Reader $ \e -> let Reader y = f (x e) in y e
ask :: Reader e e
ask = Reader id
>>=
applies the same environment to the input (x
) and the output (y
).
{-# LANGUAGE InstanceSigs #-}
import Control.Comonad
( Comonad
, (=>>)
, (=>=)
, extract
, extend
)
newtype Env e a = Env (e, a)
instance Functor (Env e) where
fmap f (Env (e, x)) = Env (e, f x)
instance Comonad (Env e) where
extract :: Env e a -> a
extract (Env (_, a)) = a
extend :: (Env e a -> b) -> Env e a -> Env e b
extend f env@(Env (e, x)) = Env (e, f env)
ask :: Env e a -> e
ask (Env (e, _)) = e
extend
takes out the environment from the input (e
) and uses it to evaluate the function (f
), and uses the same environment to create a return value too.