问题
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.
回答1:
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.
回答2:
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)
.
回答3:
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
.
回答4:
(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
来源:https://stackoverflow.com/questions/15993186/what-does-uncurry-do