Function types bound to arguments
Brent Yorgi's excellent UPenn Haskell Course presents:
fmap2 :: Functor f => (a -> b -> c) -> (f a -> f b -> f c)
fmap2 h fa fb = undefined
Types h
, fa
and fb
break into:
h :: a -> b -> c
fa :: f a
fb :: f b
It is not clear to me why this h
applies to the whole function (a -> b -> c)
.
Why h
can't link to a
and fa
to (b -> c)
?
Do the parentheses form in (a -> b -> c)
?
EDIT
Consider leftaroundabout comment :
For those who are just reading this without knowing which particular course it says:
fmap2
cannot be determined with this signature.In fact it should be
liftA2 :: Applicative a => (a->b->c) -> (f a->f b->f c)
source to share
Yes, the parentheses mean exactly the way you say it. Since (->)
right-associative but not mathematically associative , the parentheses on the left side of a function arrow cannot be separated in the way you suggest:
(a -> b) -> (f a -> f b) /= a -> b -> f a -> f b
The operator ->
in this respect is exactly the same as the exponential operator ^
, which is conditionally right-associative, but not mathematically associative :
(2 ^ 2) ^ (2 ^ 2) /= 2 ^ 2 ^ 2 ^ 2
4 ^ 4 /= 2 ^ (2 ^ (2 ^ 2))
256 /= 2 ^ (2 ^ 4)
256 /= 2 ^ 16
256 /= 65536
(The exponentiation analogy is not my own invention, function types are "exponential types" in the same sense as (a, b)
"product type" but Either a b
"sum type". But note that it is a -> b
similar b ^ a
, not a ^ b
. See .this blog post for a heavy explanation example , and also this answer provides a mathematical overview of type algebra .)
The obvious oddity with fmap2
is that the type looks like it takes one parameter, but the definition looks like it takes three. Compare this version, which at least looks more like a type signature to me:
fmap2 :: Functor f => (a -> b -> c) -> (f a -> f b -> f c)
fmap2 h = \fa fb -> undefined
Now we have a good thing with one argument fmap2 h = ...
, with the right letter "two arguments". The trick is that in Haskell these two expressions are equivalent [*]: The Haskell Report speaks of the form of a "function" with parameters in LHS, is "semantically equivalent" for simple lambda template binding.
You can also rewrite the type to exclude the parentheses to the right of the arrow, because it ->
is right-associative:
(a -> b -> c) -> (f a -> f b -> f c)
== (a -> b -> c) -> f a -> f b -> f c
as
(2 ^ 2 ^ 2) ^ (2 ^ 2 ^ 2) == (2 ^ 2 ^ 2) ^ 2 ^ 2 ^ 2
[*]: they are semantically equivalent, but when compiled with GHC, their performance characteristics can and sometimes differ. Optimizer GHC handles f x = ...
and f = \x -> ...
in different ways.
source to share