Understanding the Reader monad
I don't have a PureScript environment around currently, so I'll try to answer from a Haskell perspective, and hope it helps.
A Reader is really only a 'wrapper' around a function, so when you get a Reader r r
, you really only get a reader from r
to r
; in other words, a function r -> r
.
You can summon functions out of thin air, because, if you're a Platonist, I suppose they always exist...
When you use do
notation, you're 'inside the monad', so the context r
is implicit. In other words, you call a function that returns the r
value, and when you use the <-
arrow, you simply get that context.
You can convince yourself that it works by performing a few substitutions. First look at the signature of createUser
. Let's "unroll" the definition of Reader
:
createUser :: Reader Permissions (Maybe User)
{- definition of Reader -}
createUser :: ReaderT Permissions Identity (Maybe User)
The ReaderT
type only has one data constructor: ReaderT (r -> m a)
, which means createUser
is a term that evaluates to a value of type ReaderT (Permissions -> Identity (Maybe User))
. As you can see, it is just a function tagged with ReaderT
. It does not have to create anything out of thin air, but will receive the value of type Permissions
when that function is called.
Now let's look at the line you are having trouble with. You know that the do
notation is just syntactic sugar, and the expression:
do permissions <- ask
if hasPermission "admin" permissions
then map Just newUser
else pure Nothing
desugars to
ask >>= \permissions ->
if hasPermission "admin" permissions
then map Just newUser
else pure Nothing
To understand what this does, you will have to lookup the definition of ask
, >>=
and pure
for ReaderT
. Let's perform another round of substitutions:
ask >>= \permissions -> ...
{- definition of ask for ReaderT -}
ReaderT pure >>= \permissions -> ...
{- definition of >>= for ReaderT -}
ReaderT \r ->
pure r >>= \a -> case (\permissions -> ...) a of ReaderT f -> f r
{- function application -}
ReaderT \r ->
pure r >>= \a ->
case (if hasPermission "admin" a
then map Just newUser
else pure Nothing) of ReaderT f -> f r
{- definition of pure for Identity -}
ReaderT \r ->
Identity r >>= \a ->
case (if hasPermission "admin" a
then map Just newUser
else pure Nothing) of ReaderT f -> f r
{- definition of >>= for Identity -}
ReaderT \r ->
(\a ->
case (if hasPermission "admin" a
then map Just newUser
else pure Nothing) of ReaderT f -> f r) r
{- function application -}
ReaderT \r ->
case (if hasPermission "admin" r
then map Just newUser
else pure Nothing) of ReaderT f -> f r
As you can see, createUser
is clearly just a function wrapped by ReaderT
that threads a value (the "environment") through your expressions. runReader
unwraps the function and calls it with the provided argument:
runReader :: forall r a. Reader r a -> r -> a
runReader (ReaderT f) r = f r