McBear Holden
McBear Holden

Reputation: 833

What's the use case for the MonadReader instance for (->) r

I find the MonadReader instance for (->) r difficult to understand. Someone from irc mentions one use case for extending some polymorphic functions found in other people's package. I couldn't recall exactly what he meant. Here's an example that relates to what he said but I don't see the point. Could anyone give another example on the usecase of MonadReader for (->) r

func :: (Show a, MonadReader Int m) => Bool -> m a
func b = case b of
  True -> do
    i <- ask
    show i
  False -> "error"

main :: IO ()
main = print $ func True 5

Upvotes: 1

Views: 234

Answers (2)

K. A. Buhr
K. A. Buhr

Reputation: 50864

I'm not sure it was intended to have a use case separate from the Reader monad.

Here's some of the history...

The inspiration for the transformers library was the set of lecture notes Functional Programming with Overloading and Higher-Order Polymorphism (Mark P. Jones, 1995). In these notes, several named monads (State, Id, List, Maybe, Error, and Writer) were discussed. For example, the Writer monad type and its instance were defined as:

data Writer a = Result String a
instance Monad Writer where
    result x            = Result "" x
    Result s x ‘bind‘ f = Result (s ++ s’) y
                          where Result s’ y = f x

The reader monad was also discussed, but it wasn't defined as a separate type. Rather a Read type alias was used together with a Monad instance defined directly in terms of the partially applied function type (->) r:

type Read r = (r ->)
instance Monad (r->) where
    result x = \r -> x
    x ‘bind‘ f = \r -> f (x r) r

I don't actually know if these type-level "sections" (r ->) were valid Haskell syntax at the time. Anyway, it's not valid syntax with modern GHC versions, but that's how it appeared in the notes.

The first version of the transformers library authored by Andy Gill -- or at least the first that I was able to find, and it was actually still part of the base library at that time -- was checked into Git in June, 2001. It introduced the MonadReader class and the newtype wrapped Reader:

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

together with its Functor, Monad, MonadFix, and MonadReader instances. (No Applicative -- that hadn't been invented yet.) It also included a set of instances for (->) r with the comment:

The partially applied function type is a simple reader monad

So, I think the original formulation in the lecture notes led Andy to include these instances for (->) r, even though he also introduced a dedicated Reader newtype for consistency with the other monads in the transformers library.

Anyway, that's the history. As for use cases, I can only think of one serious one, though perhaps it isn't that compelling. The lens library is designed to interface well with MonadState and MonadReader to access complex states/contexts. Because functions like:

view :: MonadReader s m => Getting a s a -> m a 
preview :: MonadReader s m => Getting (First a) s a -> m (Maybe a) 
review :: MonadReader b m => AReview t b -> m t

are defined in terms of the MonadReader instance, they can be used both in a traditional Reader context:

do ...
   outdir <- view (config.directories.output)
   ...

and in a plain function context:

map (view (links.parent.left)) treeStructure

Again, not necessarily a compelling use case, but it's a use case.

Upvotes: 1

chepner
chepner

Reputation: 531165

The point is to make it easier to combine functions that all take the same environment.

Consider the type a -> Env -> b, where Env is some data type that contains all your "global" variables. Let's say you wanted to compose two such functions. You can't just write h = f2 . f1, because f1's return type Env -> b doesn't match f2's argument type b.

f1 :: a -> Env -> b  -- a -> ((->) Env b)
f2 :: b -> Env -> c  -- b -> ((->) Env c)

h :: a -> Env -> c
h x e = let v = f1 x e
        in f2 v e

Because there is an applicable MonadReader instance for the monad (->) Env, you can write this as

-- The class, ignoring default method implementations, is
-- class Monad m => MonadReader r m | m -> r where
--   ask :: m r
--   local :: (r -> r) -> m a -> m a
--   reader :: (r -> a) -> m a
--
-- The functional dependency means that if you try to use (->) Env
-- as the monad, Env is forced to be the type bound to r.
--
-- instance MonadReader r ((->) r) where 
--    ask = id
--    local f m = m . f
--    reader = id

h :: MonadReader Env m => a -> m c
h x = do
    v <- f1 x
    f2 v
-- h x = f1 x >>= f2

without explicit reference to the environment, which h doesn't care about; only f1 and f2 do.

More simply, you can use the Kleisli composition operator to define the same function.

import Control.Monad

h :: MonadReader Env m => a -> m c
h = f1 >=> f2

In your example, ask is simply how you get access to the environment from inside the body of the function, rather than having it as a preexisting argument to the function. Without the MonadReader instance, you would write something like

func :: Show a => Bool -> Int -> a  -- m ~ (->) Int
func b i = case b of
            True -> show i
            False -> error "nope"

The definition of main stays the same. However, (->) Int isn't the only type that has a MonadReader instance; there could be a more complicated monad stack that you are using elsewhere, which the more general type (Show a, MonadReader Int m) => Bool -> m a allows you to use instead of "just" (->) Int.

Upvotes: 2

Related Questions