Reputation: 193
This compiles successfully:
test = f id
f :: (a -> b -> c) -> String
f g = "test"
id
has type a -> a
, but f
have a first argument of type (a -> b -> c)
.
I think it should not compile. I cannot understand it.
Upvotes: 2
Views: 195
Reputation: 5348
Because you can bind the uninstantiated variable. f :: (a -> b -> c) -> String
says that for any 3 types a, b and c, f takes a function from a to b to c and returns string.
Its important to remeber that f :: (a -> b -> c) -> String
is equivalent to f :: (a -> (b -> c)) -> String
because of currying.
id
takes any type, and returns that type.
So if we swap that in, id
is returning b -> c
, and taking a
, so if a
can be b -> c
which it can as a can be any type, then this is fine.
Upvotes: 7
Reputation: 152837
Short version: it works because id
can take and return functions -- and so can behave as if it were a two-argument function. Now for the long version.
id
is a function that takes a value and returns it unchanged. Because it's so simple, it can do that for all kinds of different values -- Int
s and Bool
s and String
s and lists and trees...
id :: Int -> Int
id :: Bool -> Bool
id :: String -> String
id :: [(b, c)] -> [(b, c)]
id :: Tree b -> Tree b
id :: (b -> c) -> (b -> c)
...and functions, why not!
f
is a function that takes two-argument functions and ignores them. Because its handling of the function you give it is so simple, it can do that for all kinds of different two-argument functions -- addition, concatenation, pairing, ignoring, printing...
f :: (Int -> Int -> Int) -> String
f :: ([b] -> [b] -> [b]) -> String
f :: (b -> c -> (b, c)) -> String
f :: (b -> c -> b) -> String
f :: (Handle -> String -> IO ()) -> String
f :: ((b -> c) -> b -> c) -> String
...and the operation that takes a function and an argument and applies the one to the other, why not!
Ah, but two argument functions are really just one-argument functions that themselves return a function. So these two types are really just different spellings of the same type:
(b -> c) -> (b -> c)
(b -> c) -> b -> c
And, it turns out, the two behavior descriptions I gave for these two types also turn out to be the same. Recall they were:
I'll let you stew for a bit on why those turn out to be the same!
In any case, at this point it should be much more clear why this works. And, for the mechanical version of the answer, we can take these two types:
id :: a -> a
f :: (a -> b -> c) -> String
and make them fit together by choosing a ~ b -> c
for both. (It is just a coincidence that the two instantiations of a
are to the same type -- this is not generally the case when doing unification!) After instantiating a
to b -> c
in both type signatures, we get:
id :: (b -> c) -> b -> c
f :: ((b -> c) -> b -> c) -> String
Upvotes: 6