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