I know how to use it, but I don't understand how it is done (Reader monad)

Consider the following code (with obvious parts left)

main = do

    let s = "123456";
    let len = runReader calculateContentLength s
    putStrLn $ "Original 's' length: " ++ (show len)


calculateContentLength :: Reader String Int
calculateContentLength = do
    content <- ask           -- this seems to be the same as 'reader id'
    return (length content);

      

How to "ask" to get a string parameter? I understand that due to the declaration like

calculateContentLength :: Reader String Int

      

the 'calculateContentLength' function has a return type (of type Reader String Int), but it has no input arguments. I understand that the function itself is just one of the two arguments passed to the runReader function, but how exactly is the second parameter of runReader, 's', bound to 'ask' inside 'calculateContentLength'?

In other words, how does calculateContentLength "know" about (and access) the second argument passed by "runReader"?

+3


source to share


1 answer


Let's look at one way of defining Reader

.

newtype Reader r a = Reader { runReader :: r -> a }

      

So Reader

- this is a constructor that takes a function. This function takes a type environment r

and returns a type result a

.

ask = Reader { runReader = \env -> env }
ask = Reader id

      

The operation return

simply ignores the environment and returns.

return x = Reader { runReader = \_ -> x }

      



An operation m >>= n

does a simple ordering: it takes an environment, runs m

in that environment, then runs n

in the same environment, passing the result to it m

.

m >>= n = Reader $ \env -> let
  a = runReader m env
  in runReader (n a) env

      

So now we can take your example, delete it and shrink it step by step.

calculateContentLength = do
  content <- ask
  return (length content)

-- substitute definition of 'ask'

calculateContentLength = do
  content <- Reader id
  return (length content)

-- substitute definition of 'return'

calculateContentLength = do
  content <- Reader id
  Reader (\_ -> length content)

-- desugar 'do' into '>>='

calculateContentLength =
  Reader id >>= \content -> Reader (\_ -> length content)

-- definition of '>>='

calculateContentLength = Reader $ \env -> let
  a = runReader (Reader id) env
  in runReader ((\content -> Reader (\_ -> length content)) a) env

-- reduce lambda

calculateContentLength = Reader $ \env -> let
  a = runReader (Reader id) env
  in runReader (Reader (\_ -> length a)) env

-- definition of 'runReader'

calculateContentLength = Reader $ \env -> let
  a = id env
  in runReader (Reader (\_ -> length a)) env

-- definition of 'id'

calculateContentLength = Reader $ \env -> let
  a = env
  in runReader (Reader (\_ -> length a)) env

-- remove redundant variable

calculateContentLength = Reader $ \env
  -> runReader (Reader (\_ -> length env)) env

-- definition of 'runReader'

calculateContentLength = Reader $ \env -> (\_ -> length env) env

-- reduce

calculateContentLength = Reader $ \env -> (length env)
calculateContentLength = Reader length

      

It should now be easier to see how runReader calculateContentLength

simple is the same length

and ask

not magical - the monads operation >>=

creates a function that implicitly passes the environment for you when you run the computation with runReader

.

In fact, it is Reader

defined in terms ReaderT

that uses monadic actions instead of pure functions, but the form of its implementation is essentially the same.

+9


source







All Articles