The type (->) r a
is just an alias for r -> a
, nothing more. Its kind is *
(“type”).
The type constructor (->) r
can be thought of as r -> ___
where r
is already known but ___
is to be filled in later. Its kind is * -> *
(“type constructor”).
The (->) r
type constructor is itself is parametrized by the input type r
. To reduce the risk confusion, it might help to fix r
to something concrete. So for the rest of this answer, I’ll choose r = Int
. The explanation below will work for any choice of r
.
Both applicative functors and functors classify type constructors, not types:
- We say a
[___]
(list constructor) is a functor, Int -> ___
(functions that expect an integer) is a functor, IO ___
is a functor, Maybe ___
is a functor, etc.
- We do not say
[Int]
(list of integers) is a functor, nor do we say Int -> String
(functions expect an integer and return a string) is a functor, nor IO ()
, nor Maybe String
, etc. (Of course, in informal writing people do say this out of sloppiness, but while learning it’s important to keep this distinction in mind.)
pure :: a -> f a -- general type, where f is Applicative
pure :: a -> (->) Int a -- specialized type for f = ((->) Int)
pure :: a -> (Int -> a) -- just another way to write the above
pure x = (\_ -> x)
The pure
function creates a function that will return a
, ignoring whatever Int
it will receive. It provides a way to “wrap” things into an applicative functor.
As a concrete example, if you write pure "hi"
, this creates a function \_ -> "hi"
that always returns "hi"
no matter what input it gets. To get your "hi"
back, you apply the result to any integer, “unwrapping” the applicative functor:
>>> (pure "hi") 42
"hi"
(<*>) :: f (a -> b) -> f a -> f b -- general type, where f is Applicative
(<*>) :: (->) Int (a -> b) -> (->) Int a -> (->) Int b -- specialized type for f = ((->) Int)
(<*>) :: (Int -> (a -> b)) -> (Int -> a) -> (Int -> b) -- just another way to write the above
f <*> g = \x -> f x (g x)
The <*>
function takes two functions:
- The first function
f
will return a function a -> b
in exchange for an Int
.
- The second function
g
will return some value a
in exchange for an Int
.
The goal of <*>
is to create a new function that produces some value b
in exchange for an Int
. There is one “obvious” way to play this game: when you do get that Int
later in the future, you send it to both f
and g
and they will in exchange give you a -> b
and a
. Then you can apply the a -> b
to a
to get b
, which achieves the goal.
Here’s a concrete example: pure (+) <*> pure 2.0 <*> pure 3.0
. This expression creates a function that always yields 5.0
in exchange for whatever Int
you give. As usual you can “unwrap” it by applying it to some Int
:
>>> (pure (+) <*> pure 2.0 <*> pure 3.0) 42
5.0
But so far, none of these examples were very interesting, because they only “wrap” things using pure
, which always ignores the argument. Here’s a more interesting one:
>>> let get input = input
>>> (pure (-) <*> get <*> pure 7) 42
35
>>> (pure (-) <*> get <*> pure 7) 10
3
Compared to the previous examples, the get
function is rather unusual in that it does actually inspect the Int
argument, returning exactly what it sees. This means the result of this chain of computations differs depending on what argument it receives at the very end.
pure (-) <*> get <*> pure 7
≡ (\input -> (-)) <*> (\input -> input) <*> (\input -> 7)
≡ (\input -> (-) input) <*> (\input -> 7)
≡ (\input -> (-) input 7)
≡ (\input -> input - 7)
In fact, if you just look at the original applicative expression and squint a bit to ignore the pure
and <*>
, you can read it directly as
(-) INPUT 7
≡ INPUT - 7
where INPUT
represents the future Int
value.
Now consider your example:
(+) <$> (*2) <*> (+7)
≡ (+) <$> (\input -> input * 2) <*> (\input -> input + 7)
≡ (\input -> (+) (input * 2)) <*> (\input -> input + 7)
≡ (\input -> (+) (input * 2) (input + 7))
≡ (\input -> (input * 2) + (input + 7))
Informally this describes the following computation:
(+) (INPUT * 2) (INPUT + 7)
≡ (INPUT * 2) + (INPUT + 7)
For example, if the input is 42, you should expect 133 as the result:
>>> ((+) <$> (*2) <*> (+7)) 42
133
The purpose of the ((->) Int)
applicative functor to allow the programmer to separate the logic that depends on the input argument from the actual code that supplies the concrete value. In other words, in real code you would likely be building a rather large and complicated applicative expression, without knowing what the input will be. You would not be unwrapping the applicative value right away, but likely some far-away place in the code (perhaps in another project entirely!).
instance Applicative ((->) x) where pure v x = v ; (f <*> g) x = (f x) $ (g x)
should be much clearer.<*>
[the] same as(.)
for functions?" no, that's<$>
, so(+) <$> (*2) <*> (+7)
==(\x -> (((+).(*2) x) $ ((+7) x))
.