Let’s look at one way to define Reader
.
newtype Reader r a = Reader { runReader :: r -> a }
So Reader
is a constructor that takes a function. That function takes the environment of type r
, and returns a result of type a
.
ask = Reader { runReader = \env -> env }
ask = Reader id
The return
operation just ignores the environment and returns a value.
return x = Reader { runReader = \_ -> x }
The m >>= n
operation does simple sequencing: it takes the environment, runs m
in that environment, then runs n
in the same environment, passing it the result of m
.
m >>= n = Reader $ \env -> let
a = runReader m env
in runReader (n a) env
So now we can take your example, desugar it, and reduce 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
Now it should be easier to see how runReader calculateContentLength
is the same as just length
, and how ask
is not magical—the monad’s >>=
operation builds a function that implicitly passes the environment along for you when you run the computation with runReader
.
In reality, Reader
is defined in terms of ReaderT
, which uses monadic actions instead of pure functions, but the form of its implementation is essentially the same.