There are a lot of threads on here about deriving inferred type of composed functions but I am still fairly confused. None of the posts I found give a general explanation on how to unify types.
I have a problem on one of my study guides for an exam and I'm having trouble figuring it out.
8) What is the inferred type of (.) map uncurry :: ________
I am able to derive the inferred type most of the time but I am still sort of confused. For example, I know that to get the answer for (.) map uncurry you need to first derived the type of map uncurry. This I am able to do
Given the following types
map :: (a -> b) -> [a] -> [b]
uncurry :: (a -> b -> c) -> (a, b) -> c
I unify the function (a -> b) in map with uncurry so
a = a → b → c
b = (a, b) → c
And then the answer is the other half of map [a] -> [b] with the new values of a and b so
map uncurry :: [ a -> b -> c ] -> [ (a, b) -> c]
Then you need to unify
(.) :: (b -> c) -> (a -> b) -> a -> c
map uncurry :: [ a -> b -> c ] -> [ (a, b) -> c]
The answer is supposed to be
(.) map uncurry :: (a -> b1 -> b) -> [(a, b1)] -> [b]
but I don't understand where b1 comes from or basically how its done. I think what I really need is an explanation of type unification in general. What is the method to unify two types and how do I know if two types cannot be unified.
If anyone could give a step by step explanation of how to derive (.) map uncurry I would greatly appreciate it.
->
associates to the right. Let's work through your example.
Types
(.) :: (b -> c) -> (a -> b) -> a -> c
map :: (a -> b) -> [a] -> [b]
uncurry :: (a -> b -> c) -> (a, b) -> c
Give each function non-overlapping type names
Firstly, that's confusing because there are lots of a
s and they don't all mean the same thing, so I'm going to rename the types with new letters each time.
(.) :: (b -> c) -> (a -> b) -> a -> c
map :: (d -> e) -> [d] -> [e]
uncurry :: (f -> g -> h) -> (f, g) -> h
Bracket the types, associating to the right
(.) :: (b -> c) -> ((a -> b) -> a -> c)
map :: (d -> e) -> ([d] -> [e])
uncurry :: (f -> (g -> h)) -> ((f, g) -> h)
Now let's look at the expression (.) map uncurry
. As you've now realised, putting the operator .
in brackets converts it to a function following normal function rules, so (.) map uncurry
means ((.) map) uncurry
, and the first types to unify are from (.)
and map
.
Now (.)
has first argument (b->c)
, so (b->c)
has to unify with the type of map
:
(.) :: ( b -> c ) -> ((a -> b) -> (a -> c))
map :: (d -> e) -> ([d] -> [e])
when we substitute b ~ (d->e)
and c ~ ([d]->[e])
in the type of (.)
we get:
(.) :: ((d->e) -> ([d]->[e])) -> ((a -> (d->e)) -> (a -> ([d]->[e])))
and so map
becomes the first argument of that, so it disappears from the type signature when we supply it, giving
(.) map :: ((a -> (d->e)) -> (a -> ([d]->[e])))
Check with an interpreter like ghci or hugs
Hugs> :t (.) map
(map .) :: (a -> b -> c) -> a -> [b] -> [c]
Yup - when we add brackets due to ->
being right associative, those are the same.
OK, so now we have
(.) map :: ((a -> (d->e)) -> (a -> ([d]->[e])))
uncurry :: (f -> (g -> h)) -> ((f, g) -> h)
Now it's terribly tempting to match those two first arguments since they look the same, but of course we need to match the first argument of (.) map
with the whole type of uncurry
:
Match the type of the first argument with the whole of that argument's type
(.) map :: (( a -> ( d -> e)) -> (a -> ([d]->[e])))
uncurry :: (f->(g->h)) -> ((f,g) -> h)
giving a ~ (f->(g->h))
, d ~ (f,g)
and e ~ h
:
(.) map :: (((f->(g->h)) -> ((f,g)-> h)) -> ((f->(g->h)) -> ([(f,g)]->[h])))
and applying that to uncurry gives
(.) map uncurry :: ((f->(g->h)) -> ([(f,g)]->[h])))
Check with an interpreter
Hugs> :t (.) map uncurry
map . uncurry :: (a -> b -> c) -> [(a,b)] -> [c]
Great - we did it!
If we take the example length . map (.) $ repeat id ++ [] ++ []
length . map (.) $ repeat id ++ [] ++ []
, we're going to need the fixities of all those operators, but we can bracket the function applications first because they take precedence:
length . map (.) $ repeat id ++ [] ++ []
length . (map (.)) $ (repeat id) ++ [] ++ []
Put the operators in order of precedence
Look up the fixities of the operators using the :i
command of your interpreter, and put them in order, highest first:
infixr 9 .
infixr 5 ++
infixr 0 $
The highest precedence operator .
gets bracketed first:
(length . (map (.))) $ (repeat id) ++ [] ++ []
Then ++
, which associates to the right:
(length . (map (.))) $ ((repeat id) ++ ([] ++ []))
and there's only one use of $
so I've not bothered bracketing it.
If you like, you can convert operators like ++
to functions (++)
, but I'm not at all convinced that will help you, so I'd leave it be, just remembering that the first argument is to the left.
It'll usually help to start with the most nested brackets.
(.) map uncurry
is equivalent to ((.) map) uncurry
. The map
function is not applied to uncurry
, it is passed to (.)
. Then the result, which you will hopefully infer to be a function, receives uncurry
as argument.
As for where the b1
come from, don't forget that type variable names are not important, you can rename them as long as you similarly rename all occurrences of a given type variable. So this:
(.) map uncurry :: (a -> b1 -> b) -> [(a, b1)] -> [b]
is equivalent to this:
(.) map uncurry :: (apple -> pear -> plum) -> [(apple, pear)] -> [plum]
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.