1
votes

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.

2
There is a precedence mixup in your derivation. (.) map uncurry is (.) with map and uncurry as arguments, and not (.) (map uncurry).duplode
Thank you. I'm having trouble deriving the type of (.) map. Could anyone help explain how to get it.user3450307
Never mind I was able to figure it out. ( .) :: (b -> c) -> (a -> b) -> a -> c map :: (a1 → b1) → [a1] → [b1] b = (a1 → b1) c = [a1] - [b1] (a -> a1 → b) -> a -> [a1] -> [b]user3450307

2 Answers

6
votes

Keys idea in deriving type signatures:

  1. Make sure you get your operator and function precedences right before you start.
    Notice that function application has the highest precedence and associates to the left, so:
  2. The first argument is the one that matters most - deal with it first, and
  3. In type signatures, -> associates to the right.
  4. Once you have the type of the first argument, substitute that type everywhere it appears.

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 as 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)

Match the type of the first argument with the whole of that argument's type

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.

Move on to the next argument

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!

Dealing with operators

If we take the example 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.

0
votes

(.) 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]