(Make sure you understand higher-order functions and currying, read Learn You a Haskell chapter on higher-order functions, then read difference between . (dot) and $ (dollar sign) and function composition (.) and function application ($) idioms)
($)
is just a function application, f $ x
is equivalent to f x
. But that's good, because we can use explicit function application, for example:
map ($2) $ map ($3) [(+), (-), (*), (**)] -- returns [5.0,1.0,6.0,9.0]
which is equivalent to:
map (($2) . ($3)) [(+), (-), (*), (**)] -- returns [5.0,1.0,6.0,9.0]
Check the type of ($)
: ($) :: (a -> b) -> a -> b
. You know that type declarations are right-associative, therfore the type of ($)
can also be written as (a -> b) -> (a -> b)
. Wait a second, what's that? A function that receives an unary function and returns an unary function of the same type? This looks like a particular version of an identity function id :: a -> a
. Ok, some types first:
($) :: (a -> b) -> a -> b
id :: a -> a
uncurry :: (a -> b -> c) -> (a, b) -> c
uncurry ($) :: (b -> c, b) -> c
uncurry id :: (b -> c, b) -> c
When coding Haskell, always look at types, they give you lots of information before you even look at the code. So, what's a ($)
? It's a function of 2 arguments. What's an uncurry
? It's a function of 2 arguments too, the first being a function of 2 arguments. So uncurry ($)
should typecheck, because 1st argument of uncurry
should be a function of 2 arguments, which ($)
is. Now try to guess the type of uncurry ($)
. If ($)
's type is (a -> b) -> a -> b
, substitute it for (a -> b -> c)
: a
becomes (a -> b)
, b
becomes a
, c
becomes b
, therefore, uncurry ($)
returns a function of type ((a -> b), a) -> b
. Or (b -> c, b) -> c
as above, which is the same thing. So what does that type tells us? uncurry ($)
accepts a tuple (function, value)
. Now try to guess what's it do from the type alone.
Now, before the answer, an interlude. Haskell is so strongly typed, that it forbids to return a value of a concrete type, if the type declaration has a type variable as a return value type. So if you have a function with a type a -> b
, you can't return String
. This makes sense, because if your function's type was a -> a
and you always returned String
, how would user be able to pass a value of any other type? You should either have a type String -> String
or have a type a -> a
and return a value that depends solely on an input variable. But this restriction also means that it is impossible to write a function for certain types. There is no function with type a -> b
, because no one knows, what concrete type should be instead of b
. Or [a] -> a
, you know that this function can't be total, because user can pass an empty list, and what would the function return in that case? Type a
should depend on a type inside the list, but the list has no “inside”, its empty, so you don't know what is the type of elements inside empty list. This restriction allows only for a very narrow elbow room for possible functions under a certain type, and this is why you get so much information about a function's possible behavior just by reading the type.
uncurry ($)
returns something of type c
, but it's a type variable, not a concrete type, so its value depends on something that is also of type c
. And we see from type declaration that the function in the tuple returns values of type c
. And the same function asks for a value of type b
, which can only be found in the same tuple. There are no concrete types nor typeclasses, so the only thing uncurry ($)
can do is to take the snd
of a tuple, put it as an argument in function in fst
of a tuple, return whatever it returns:
uncurry ($) ((+2), 2) -- 4
uncurry ($) (head, [1,2,3]) -- 1
uncurry ($) (map (+1), [1,2,3]) -- [2,3,4]
There is a cute program djinn that generates Haskell programs based on types. Play with it to see that our type guesses of uncurry ($)
's functionality is correct:
Djinn> f ? a -> a
f :: a -> a
f a = a
Djinn> f ? a -> b
-- f cannot be realized.
Djinn> f ? (b -> c, b) -> c
f :: (b -> c, b) -> c
f (a, b) = a b
This shows, also, that fst
and snd
are the only functions that can have their respective types:
Djinn> f ? (a, b) -> a
f :: (a, b) -> a
f (a, _) = a
Djinn> f ? (a, b) -> b
f :: (a, b) -> b
f (_, a) = a