13

I'm reading PureScript by Example and got to the part introducing the Reader monad. The example goes like this:

createUser :: Reader Permissions (Maybe User)
createUser = do
  permissions <- ask
  if hasPermission "admin" permissions
    then map Just newUser
    else pure Nothing

The confusing part for me is the ask function. The signature is:

ask   :: forall r. Reader r r

It appears as if it creates a Reader out of thin air

When I was reading about the State monad, it had the same concept with its get function. And the text explained:

the state is implemented as a function argument hidden by the State monad’s data constructor, so there is no explicit reference to pass around.

I'm guessing this is the key, and the same thing is happening here with the Reader, but I don't understand how it works...

When the above example is run via runReader, how does the provided value suddenly appear as a result of ask? The Haskell docs for ask say: Retrieves the monad environment. But my confusion is from where? The way I see it, a value gets passed to runReader, gets stored somewhere, and to get it - you call ask... but that makes no sense.

While the example is PureScript, I'm guessing any Haskell-literate person would also be able to answer, hence the Haskell tag.

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
kaqqao
  • 12,984
  • 10
  • 64
  • 118
  • 2
    `Reader a b` is a wrapper around `a -> b`. So, `ask :: forall a. Reader a a` is ultimately just a value of type `forall a. a -> a`, with a wrapper around it. – David Young Oct 13 '17 at 21:36

3 Answers3

13

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.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • 2
    Upvoted. Probably worth nothing that the way to summon such an `r -> r` function from thin air is to utter the incantation `id`. (And `id` is the _only_ such function, thanks to parametricity.) – Benjamin Hodgson Oct 13 '17 at 21:44
  • Ok, so in my case the Reader is a wrapper around Permissions -> Maybe User. When I run `runReader createUser permissions`, how does `ask` in the body of `createUser` know to return the same `permissions` I passed to `runReader`? I'm sure the question is downright nonsensical as I'm looking at this all wrong... But please try helping me unbend my brain. – kaqqao Oct 13 '17 at 21:46
  • 1
    @kaqqao The type `Reader Permissions (Maybe User)` is just a 'wrapper' over `Permissions -> (Maybe User)`, so your entire `createUser` 'value' is really a function (but functions are values, so that's cool). When you call `runReader`, you must pass not only `createUser`, but also a value for `r` - in this case a `Permissions` value. `runReader` then calls the wrapped function with the `Permissions` value you passed it. HTH. – Mark Seemann Oct 13 '17 at 21:51
  • @MarkSeemann Aah, I see. `runReader` effectively sets the state/environment on the Reader instance before invoking the function inside it. (Not sure how good is my terminology) It finally makes sense. Thanks a bunch! – kaqqao Oct 13 '17 at 23:22
  • Why should someone disguise the information flow within a composition of pure functions with the monad machinery? Only to abstract from some arguments? Isn't that too high a price? –  Oct 15 '17 at 11:42
  • 1
    @ftor I don't think the point of the `Reader` monad is to *disguise* anything. The `Reader` monad exists, mathematically, so it might as well also exist in the Haskell/TypeScript API. This enables you to use a reader (i.e. a pure function) with all functions that operate on monads. (e.g. `mapM`, `sequence`, and so on...). The motivation isn't to hide anything, but to give you *options* for composition. – Mark Seemann Oct 15 '17 at 12:11
4

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
Regis Kuckaertz
  • 991
  • 5
  • 14
0

The partial function type (->) r is a functor i.e. r->a is a container for any type a (a List a of size two is equivalent to a function Bool->a) . Moreover, it is also a monad

instance Monad ((->) r) where
    f >>= k = \ r -> k (f r) r

It satisfies the MonadReader type class and is called the simple reader monad and might be given the type synonym Reader r.

ask returns this monad (->) r) r applied to the same type r, which we then can bind.

Understand the partial function type (->) r better.

Tom Huntington
  • 2,260
  • 10
  • 20