Today I had a few hours of fun trying to understand what the arrow operator application in Haskell does. Now I am trying to verify the correctness of my understanding. In short, I found that to use the arrow operator
(f <*> g <*> h <*> v) z = f z (g z) (h z) (v z)
Before continuing, I know this discussion , but found that it is very confusing and much more complicated than I hope I got today.
To understand that applicative I started by defining the applicative arrow in the base
instance Applicative ((->) a) where
pure = const
(<*>) f g x = f x (g x)
and then continued to study what expressions
(f <*> g <*> h) z
and
(f <*> g <*> h <*> v) z
output when expanding.
From the definition we get that
f <*> g = \x -> f x (g x)
Since it (<*>)
remains associative, then
f <*> g <*> h = (f <*> g) <*> h
= (\x -> f x (g x)) <*> h
= \y -> (\x -> f x (g x)) y (h y)
therefore
(f <*> g <*> h) z = (\y -> (\x -> f x (g x)) y (h y)) z
= (\x -> f x (g x)) z (h z)
= (f z (g z)) (h z)
= f z (g z) (h z)
The last step is related to the fact that the application function remains associative. Similarly
(f <*> g <*> h <*> v) z = f z (g z) (h z) (v z)
This, for me, gives a very clear intuitive idea of what the application of the arrow does. But is this true?
To check the result, I performed, for example, the following:
λ> ((\z g h v -> [z, g, h, v]) <*> (1+) <*> (2+) <*> (3+)) 4
[4,5,6,7]
which corresponds to the result obtained above.
Before doing the extension above, I found this application very difficult to understand, because extremely complex behavior can be the result of its use due to currying. In particular, in
(f <*> g <*> h <*> v) z = f z (g z) (h z) (v z)
Functionsmay return other functions. Here is an example:
λ> ((\z g -> g) <*> pure (++) <*> pure "foo" <*> pure "bar") undefined
"foobar"
In this case, it is z=undefined
ignored by all functions, because the pure x z = x
first function also ignores the construction z
. In addition, the first function accepts only two arguments, but returns a function that takes two arguments.