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 (->)?
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With