Consider the following code (with obvious parts left out)
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 does 'ask' get at the string parameter? It's my understanding that because of the type declaration
calculateContentLength :: Reader String Int
the function 'calculateContentLength' has a return type (of type Reader String Int) but it has no incoming arguments. I realize that the function itself is simply one of two arguments being passed into the runReader function but how exactly does the second parameter to runReader, 's', get tied to 'ask' inside 'calculateContentLength'?
In other words, how does 'calculateContentLength' "know" about (and get access to) the second argument passed with 'runReader'?
Let’s look at one way to define
Reader
.So
Reader
is a constructor that takes a function. That function takes the environment of typer
, and returns a result of typea
.The
return
operation just ignores the environment and returns a value.The
m >>= n
operation does simple sequencing: it takes the environment, runsm
in that environment, then runsn
in the same environment, passing it the result ofm
.So now we can take your example, desugar it, and reduce it step by step.
Now it should be easier to see how
runReader calculateContentLength
is the same as justlength
, and howask
is not magical—the monad’s>>=
operation builds a function that implicitly passes the environment along for you when you run the computation withrunReader
.In reality,
Reader
is defined in terms ofReaderT
, which uses monadic actions instead of pure functions, but the form of its implementation is essentially the same.