Search code examples
haskellmonads

Reader Monad in Haskell. Where is the reader passed as argument?


On this monad reader example:

import Control.Monad.Reader

tom :: Reader String String
tom = do
    env <- ask -- gives you the environment which in this case is a String
    return (env ++ " This is Tom.")

jerry :: Reader String String
jerry = do
  env <- ask
  return (env ++ " This is Jerry.")

tomAndJerry :: Reader String String
tomAndJerry = do
    t <- tom
    j <- jerry
    return (t ++ "\n" ++ j)

runJerryRun :: String
runJerryRun = (runReader tomAndJerry) "Who is this?"

we can read the ˋdoˋ notation as

tomAndJerry = tom >>= \t -> ...

where ultimately you get a String back. Ok, so, basically ˋ(runReader tomAndJerry) "Who is this?"ˋ is a reader such that ˋaskˋ gives ˋWho is this?ˋ. However, ˋtomˋ has no argument names. How is ask able to run?

Can I think of ˋtom :: Reader String Stringˋ as something like C++ generics where ˋReaderˋ is bound to some static class in compile time, which always produces "Who is this?" when you call ask on it? Because as far as I know, ˋaskˋ is something that takes a ˋReader String Stringˋ and it's not explicitly getting it anywhere as an argument.

I know that it gets the context somehow, but I'm intersted in how it happens in the language itself.

I didn't try anything as it's not a problem itself


Solution

  • A thing of type Reader r a is, in reality, not a value of type a, but a function of type r -> a that takes an r environment and produces a value of type a. Check out how many of your questions go away if I make this simple substitution in your code:

    -- skip all imports, so we can use our own ask implementation
    
    ask :: String -> String
    ask s = s
    
    tom :: String -> String
    tom = do
        env <- ask -- gives you the environment which in this case is a String
        return (env ++ " This is Tom.")
    
    jerry :: String -> String
    jerry = do
      env <- ask
      return (env ++ " This is Jerry.")
    
    tomAndJerry :: String -> String
    tomAndJerry = do
        t <- tom
        j <- jerry
        return (t ++ "\n" ++ j)
    

    Where does ask get its environment from? Well, it's a function, and it takes the environment as a parameter, that's where! Same for tom: it gets its environment passed to it as an argument.

    Well, you may say, but tom = do {- ... -} doesn't look like a function that takes an argument. After all, there's no argument there on the left of the = sign!

    You're right. It doesn't look like a function to a traditional imperative programmer. Nevertheless, it is. Don't worry... you'll get used to it over time! Just to give a really stupid example:

    f = (+)
    

    Although this doesn't look like a function (it doesn't have arguments to the left of the = sign), it is a function. It is exactly the same function as (+) is. The very similar definitions f x y = x + y or f x y = (+) x y, which look more like a function, mean almost exactly the same thing. A slightly less stupid example:

    g = if somethingComplicated then (+) else (*)
    

    Functions are simply objects that you can pass around, store in variables, return as the result of complicated calculations, etc. They don't always need to name their arguments to be a function!

    Now let's walk through your post a bit.

    Ok, so, basically (runReader tomAndJerry) "Who is this?" is a reader such that ask gives Who is this?.

    I don't like this phrasing. It is just tomAndJerry that is a reader; the fuller phrase (runReader tomAndJerry) "Who is this?" is a String, not a reader.

    However, tom has no argument names. How is ask able to run?

    There isn't any named argument, but there is still an argument.

    Can I think of tom :: Reader String String as something like C++ generics where Reader is bound to some static class in compile time, which always produces "Who is this?" when you call ask on it?

    Definitely not. ask is not a thing you can pass a Reader argument to; it is already a Reader action all by itself. You can't call ask on tom. You can think of tom :: Reader String String as something like tom :: String -> String. You can also think of ask as something like ask :: String -> String that can be used during the implementation of tom if you want (but no obligation).

    Because as far as I know, ask is something that takes a Reader String String and it's not explicitly getting it anywhere as an argument.

    No. See previous point. ask is a Reader String String, it doesn't take one.