I'm doing some excersises where I have to add a function's type and explain what it does. I'm stuck with this:
phy = uncurry ($)
The type, according to GHCi is phy :: (a -> b, a) -> b
. My haskell knowledge is basic so I really have no idea what it does.
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.
Elementary HaskellA function that takes another function (or several functions) as an argument is called a higher-order function.
flip f takes its (first) two arguments in the reverse order of f.
uncurry :: (a -> b -> c) -> (a, b) -> c
($) :: (a -> b) -> a -> b
uncurry ($) :: (a -> b, a) -> b
If you inspect types of uncurry
and $
and its description:
uncurry converts a curried function to a function on pairs.
All it does is it takes a function (a -> b -> c)
and returns a function that takes the parameters as a tuple.
So phy
does the same thing as $
, but instead of f $ x
or ($) f x
you call it like phy (f, x)
.
Let's spell out the type part systematically. We'll start with the types of uncurry
and ($)
:
uncurry :: (a -> b -> c) -> (a, b) -> c
($) :: (a -> b) -> a -> b
Since the target expression has ($)
as the argument of uncurry
, let's line up their types to reflect this:
uncurry :: (a -> b -> c) -> (a, b) -> c
($) :: (a -> b) -> a -> b
The whole type of ($)
lines up with the first argument type of uncurry
, and the argument and result types of ($)
line up with those of uncurry
's first argument as shown. This is the correspondence:
uncurry's a <==> ($)'s a -> b
uncurry's b <==> ($)'s a
uncurry's c <==> ($)'s b
This is kinda confusing, because the a
and b
type variables in one type are not the same as in the other (just like the x
in plusTwo x = x + 2
is not the same as the x
in timesTwo x = x * 2
). But we can rewrite the types to help up reason about this. In simple Haskell type signatures like this, any time you see a type variable you can replace all of its occurrences with any other type get a valid type as well. If you pick fresh type variables (type variables that don't appear anywhere in the original), you get an equivalent type (one that can be converted back to the original); if you pick a non-fresh type you get a specialized version of the original that works with a narrower range of types.
But anyway, let's apply this to the type of uncurry
::
-- Substitute a ==> x, b ==> y, c ==> z:
uncurry :: (x -> y -> z) -> (x, y) -> z
Let's redo the "line up" using the rewritten type:
uncurry :: (x -> y -> z) -> (x, y) -> z
($) :: (a -> b) -> a -> b
Now it's obvious: x <==> a -> b
, y <==> a
and z <==> b
. Now, substituting uncurry
's type variables for their counterpart types in ($)
, we get:
uncurry :: ((a -> b) -> a -> b) -> (a -> b, a) -> b
($) :: (a -> b) -> a -> b
And finally:
uncurry ($) :: (a -> b, a) -> b
So that's how you figure out the type. How about what it does? Well, the best way to do that in this case is to look at the type and think about it carefully, figuring out what we'd have to write to get a function of that type. Let's rewrite it this way to make it more mysterious:
mystery :: (a -> b, a) -> b
mystery = ...
Since we know mystery
is a function of one argument, we can expand this definition to reflect that:
mystery x = ...
We also know that its argument is a pair, so we can expand a bit more:
mystery (x, y) = ...
Since we know that x
is a function and y :: a
, I like to use f
to mean "function" and to name variables the same as their type—it helps me reason about the functions, so let's do that:
mystery (f, a) = ...
Now, what do we put in the right hand side? We know it must be of type b
, but we don't know what type b
is (it's actually whatever the caller chooses, so we can't know). So we must somehow make a b
using our function f :: a -> b
and value a :: a
. Aha! We can just call the function with the value:
mystery (f, a) = f a
We wrote this function without looking at uncurry ($)
, but it turns out that it does the same thing as uncurry ($)
does, and we can prove it. Let's start with the definitions of uncurry
and ($)
:
uncurry f (a, b) = f a b
f $ a = f a
Now, substituting equals for equals:
uncurry ($) (f, a) = ($) f a -- definition of uncurry, left to right
= f $ a -- Haskell syntax rule
= f a -- definition of ($), left to right
= mystery (f, a) -- definition of mystery, right to left
So one way to attack a type that you don't understand in Haskell is to just try and write some code that has that type. Haskell is different from other languages in that very often this is a better strategy than trying to read the code.
The other two answers are fine. I just have a slightly different take on it.
uncurry :: (a -> b -> c) -> (a, b) -> c
($) :: (a -> b) -> a -> b
Since the "->" in type signatures associates to the right, I can equivalently write these two type signatures like this:
uncurry :: (a -> b -> c) -> ((a, b) -> c)
($) :: (a -> b) -> (a -> b)
uncurry
takes an arbitrary function of two inputs and changes it into a funciton of one argument where that argument is a tuple of the original two arguments.
($)
takes a simple one-argument function and turns it into...itself. Its only effect is syntactical. f $
is equivalent to f
.
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