Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Intrigued by (->) as instances of monad and functor

Tags:

haskell

I am quite intrigued by (->) when I looked up in information about (->) in ghci. It says,

data (->) a b -- Defined in `GHC.Prim`

So far so good, but then it gets very interesting when it says -

instance Monad ((->) r) -- Defined in `GHC.Base`
instance Functor ((->) r) -- Defined in `GHC.Base`

What does this implicate? Why does GHC define it as an instance of a Monad, and Functor for (->)?

like image 730
Yogesh Sajanikar Avatar asked Jan 09 '14 16:01

Yogesh Sajanikar


2 Answers

It can be a bit confusing at first, but one important concept to remember is that (->) is not a monad or functor, but (->) r is. Monad and Functor types all have the kind * -> *, so they only expect one type parameter.

What this means is that fmap for (->) r looks like

fmap g func = \x -> g (func x)

Which is also known as

fmap g func = g . func

which is just normal function composition! When you fmap g over func, you change the output type by applying g to it. In this case, if func has the type a -> b, g must have a type like b -> c.

The Monad instance is bit more interesting. It lets you use the result of a function application "before" that application has occurred. What helped me understand was seeing an example like

f :: Double -> (Double,Double)
f = do
    x1 <- (2*)
    x2 <- (2+)
    return (x1, x2)

> f 1.0
(2.0, 3.0)

What this does is apply the implicit argument to f to each of the functions on the right hand side of the binds. So if you pass in 1.0 to f, it will bind the value 2 * 1.0 to x1 and binds 2 + 1.0 to x2, then returns (x1, x2). It really makes it easy to apply a single argument to many subexpressions. This function is equivalent to

f' x = (2 * x, 2 + x)

Why is this useful? One common use is the Reader monad, which is simply a newtype wrapper around (->) r. The Reader monad makes it easy to apply a static global config across your application. You could write code like

myApp :: Reader Config ()
myApp = do
    config <- ask
    -- Use config here
    return ()

And then you run your application with runReader myApp initialConfig. You can easily write actions in the Reader Config monad, compose them, chain them together, and all of them have access to the global, readonly config. In addition, there's a companion ReaderT monad transformer which allows you to build it into your transformer stack, letting you have very complex applications that have easy access to static configuration.

like image 84
bheklilr Avatar answered Sep 27 '22 21:09

bheklilr


I think it would be a bit less confusing if Haskell had just always allowed type operator sections:

data a->b

instance Monad (r -> )

looks much more natural.

For short explanation: I find it quite helpful to consider the special case Monad (Bool -> ), which is basically a two-element container type. It has the two elements

\case
  False -> elem1
  True -> elem2

So you can think about the functor instance much the same as for lists: map over "all contained elements".

The applicative and monad instances are a bit different, it might help to make the "container transformation" explicit:

data Pair a = Pair a a

instance Functor Pair where
  fmap f (Pair a b) = Pair (f a) (f b)

instance Monad Pair where
  return a = Pair a a
  join (Pair (Pair a _) (Pair _ b))
      = Pair       a            b
like image 20
leftaroundabout Avatar answered Sep 27 '22 20:09

leftaroundabout