Reputation: 99
While I try to familiar the usage for Reader monad in Haskell, I noticed there are two similar functions provided in the mtl
library.
asks :: MonadReader r m => (r -> a) -> m a
local :: MonadReader r m => (r -> r) -> m a -> m a
For example,
import Control.Monad.Reader
changeEnv :: String -> String
changeEnv = ("Prefix " ++)
getLength :: Reader String Int
getLength = do
e <- ask
return $ length e
runReader (local changeEnv getLength) "123"
10
runReader (asks (length . changeEnv)) "123"
10
runReader (asks (length . local changeEnv id)) "123"
10
It seems we can compute the same result using either asks
or local
.
Is there any result that cannot compute from either one of them?
For now, I can only say using local
allows me to separate the function (changeEnv
) changing the environment and a function (getLength
) that returns the result of the Reader. However, the same could have achieve using asks
as well.
Is there any special reason that we need both of them?
Upvotes: 1
Views: 605
Reputation: 116139
In your case, you were able to replace local changeEnv getLength
with an equivalent expression involving asks
. However, to do so, you had to inspect the code of getLength
and manually insert changeEnv
every time the code used ask
.
The point of local
is to allow that transformation without having to rewrite all the code of getLength
. Imagine an extreme scenario where instead of getLength
we have a a very complex action
, possibly also invoking other actions, so that in total we touch a few thousand lines of code.
Then we find a local f action
we would like to replace. In such case, we would need to rewrite all of the code of action
and its dependencies, just to insert f
in all the right places. This is long, and also inefficient since in this way we recompute f
many times.
local
avoids that. Of course, strictly speaking, we do not have to use local
, since we could break the abstraction by pattern matching on the Reader
constructor (or abuse runReader
) so that we can pre-compose f
at the right spot. In doing so, however, we would have reimplemented local
.
Further, note that local
works not only on the simple Reader a
monad, but on all monads of class MonadReader
, where the action
s could very well be complex ones.
Upvotes: 0
Reputation: 2818
We have asks f = local f ask
for any f, so it's true that you could
always write asks
in terms of local
. Though normally you would
think of asks f
as a shorthand for fmap f ask
.
In the other direction however, consider something like
local changeEnv (do x <- getLength
y <- getReverse
pure (replicate x y))
How do you do this with asks
in general? You'd have to include the
entire body and put the environment into it explicitly:
asks ((\s -> replicate (length s) (reverse s)) . changeEnv)
You can always do this if you want because you can always translate any Reader computation into ordinary functions. But then you might as well not use Reader at all.
This doesn't mean local
is necessary either - it's just a
convenience for nesting readers, and you can always use runReader
directly instead like:
do s' <- asks changeEnv
let x = runReader (do ...) s'
...
and I suppose you can then cram it all inside asks
if you really want to, like
asks (runReader (do ...) . changeEnv)
but using local
seems nicer.
Upvotes: 1