Say I define this function:
f = ($ 5)
Then I can apply it:
> f (\x -> x ^ 2)
25
Its type is:
:t f
f :: (Integer -> b) -> b
Which makes sense, it gets a function as an argument, and returns this function applied on the Integer
5
.
Now I define this function:
g = flip f
I would expect this to not make sense, because f
is a function of a single argument.
But, checking its type:
:t g
g :: b -> (Integer -> b -> c) -> c
So now g
is a function of 2 arguments!
Applying it on some values:
> g [2, 4, 6] (\x y -> x:y)
[5,2,4,6]
What is going on here? What does flip ($ 5)
really mean?
flip f takes its (first) two arguments in the reverse order of f. flip the order of the first two arguments of a function.
a -> b Bool means... forall (a :: *) (b :: * -> *). a -> b Bool. b is therefore a type constructor taking a single type argument. Examples of single-argument type constructors abound: Maybe , [] , IO are all examples of things which you could use to instantiate b .
From HaskellWiki. Currying is the process of transforming a function that takes multiple arguments in a tuple as its argument, into a function that takes just a single argument and returns another function which accepts further arguments, one by one, that the original function would receive in the rest of that tuple.
Follow the types:
($ 5) :: (Int -> a) -> a
flip :: (x -> y -> z) -> y -> x -> z
But since ->
is right associative, the type x -> y -> z
is equivalent to x -> (y -> z)
, so
flip :: (x -> (y -> z)) -> y -> x -> z
($ 5) :: (Int -> a) -> a
So x ~ (Int -> a)
and (y -> z) ~ a
, so substituting back in:
($ 5) :: (Int -> (y -> z)) -> (y -> z)
And simplified
($ 5) :: (Int -> y -> z) -> y -> z
So
flip ($ 5) :: y -> (Int -> y -> z) -> z
Which is equivalent to the type you're seeing (although I used Int
instead of Integer
to save typing).
What this is saying is that the type of ($ 5)
gets specialized when passed to flip
such that it takes a function of 2 arguments. It is perfectly valid to have something like ($ 5) const
, where const :: a -> b -> a
and ($ 5) const :: b -> Int
. All ($ 5)
is doing is applying 5
as an argument to a function, not necessarily the argument for a function. This is an example of partial application, where not all of the arguments are supplied to a function. That's why you can do things like map (subtract 1) [1, 2, 3]
.
An example of how to use flip ($ 5)
is:
> flip ($ 5) 2 (**)
25.0
> flip ($ 5) 1 (-)
4.0
> let f x y = (x, y)
> flip ($ 5) 1 f
(5, 1)
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