简体   繁体   中英

Calculating the type of `map . foldr`

map :: (a -> b) -> [a] -> [b]
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b

What is a systematic way to figure out the type for map . foldr map . foldr ? I know how to do it for map foldr but get confused when it comes to a composition.

Thanks!

Obviously there must be a systematic way, otherwise the Haskell compiler could not do type inference.

One way we can do this ourselves is insert the types step by step:

We have the following types:

(.) :: (b -> c) -> (a -> b) -> (a -> c)
map :: (a' -> b') -> [a'] -> [b']
foldr :: Foldable t => (a'' -> b'' -> b'') -> b'' -> t a'' -> b''

Note that you have to choose different names for types appearing in different signatures for this to work out.

1. supply map to (.)

If we supply a generic function f to (.) we get the following types:

(.) :: (b -> c) -> (a -> b) -> (a -> c)
(.) f :: (a -> b) -> (a -> c)
f :: (b -> c)

choose f to be map :

map :: (a' -> b') -> [a'] -> [b']

equal to

map :: (a' -> b') -> ([a'] -> [b'])

as f has type (b -> c) we can conclude:

b :: (a' -> b')
c :: ([a'] -> [b'])

insert our inferred types:

(.) f :: (a -> b) -> (a -> c)
(.) map :: (a -> (a' -> b')) -> (a -> ([a'] -> [b']))

we can drop some parentheses:

(.) map :: (a -> (a' -> b')) -> a -> ([a'] -> [b'])
(.) map :: (a -> (a' -> b')) -> a -> [a'] -> [b']
(.) map :: (a -> a' -> b') -> a -> [a'] -> [b']

2. supply foldr to (.) map

Again start by suppling a generic function g :

(.) map :: (a -> a' -> b') -> a -> [a'] -> [b']
(.) map g :: a -> [a'] -> [b']
g :: (a -> a' -> b')

choose g to be foldr :

foldr :: Foldable t => (a'' -> b'' -> b'') -> b'' -> t a'' -> b''

equal to

foldr :: Foldable t => (a'' -> b'' -> b'') -> b'' -> (t a'' -> b'')

as g has type (a -> a' -> b') we can conclude:

a :: (a'' -> b'' -> b'')
a' :: b''
b' :: Foldable t => t a'' -> b''

insert our inferred types:

(.) map foldr :: a -> [a'] -> [b']
(.) map foldr :: Foldable t => (a'' -> b'' -> b'') -> [b''] -> [t a'' -> b'']

Which is the same type we get, when asking ghci for the type:

> :t ((.) map foldr)
((.) map foldr) :: Foldable t => (a1 -> a2 -> a2) -> [a2] -> [t a1 -> a2]

map . foldr map . foldr is actually (.) map foldr . Adding the type of (.) into the mix we get

        foldr :: Foldable t =>           (a -> (r->r)) -> (r -> (t a -> r))
    map :: (i -> j) -> ([i] -> [j])
(.) ::    (   b     ->      c      ) -> (    d         ->     b            ) -> (d -> c)
-----------------------------------------------------------------------------------------
--            4             2                1                3
-----------------------------------------------------------------------------------------
(.) map foldr :: Foldable t =>                                                  (d -> c)
    where                        d ~ a -> (r -> r)       -- 1
                                 c ~ [i] -> [j]          -- 2
                                 b ~ r -> (t a -> r)     -- 3
                                   ~ i ->      j         -- 4
                                 -------------------
                                 i ~ r                   -- 5
                                 j ~       t a -> r      -- 6

thus

map . foldr :: Foldable t => a -> (r -> r) -> [i] -> [j]          -- by 1,2
            ~  Foldable t => a -> (r -> r) -> [r] -> [t a -> r]   -- by 5,6

Here we used the application type derivation rule,

     f   :: A -> B
       x :: A
    ---------------
     f x ::      B

(otherwise known as modus ponens, in logic).

We could also use a composition type derivation rule which is the application rule specialized for (.) , or equivalently (>>>) = flip (.) :

           g ::      B -> C
     f       :: A -> B
    ------------------------
     f >>> g :: A ->      C
     g  .  f :: A ->      C

To fit this pattern, we write the types down a bit differently, and obtain the result immediately :

          map ::                                (i ->      j    ) -> ([i] -> [    j   ])
foldr         :: Foldable t => (a -> (r->r)) -> (r -> (t a -> r))
------------------------------------------------------------------------------------
foldr >>> map :: Foldable t => (a -> (r->r)) ->                       [r] -> [t a -> r]
map  .  foldr :: Foldable t => (a -> (r->r)) ->                       [r] -> [t a -> r]

It is much more intuitive this way.

Ok, rather than using an automatic method to infer the type I thought maybe you'll be interested in a more intuitive answer:

As I'm sure you know, map . foldr map . foldr is equivalent to (\\x -> map (foldr x)) . Let's start with that.

What should be the type of x ? Well, since it's the first parameter to foldr , it should look like a function that takes some value, some accumulator, and return something of the same type as the accumulator (by definition of foldr ). Thus :

x :: (a -> b -> b)

Now that we have the type of the first parameter, let's look at the rest.

Once (foldr x) is applied, we get back a function that stills waits for an initial accumulator value, and then for any foldable type, and returns a value of the same type as the accumulator (for example, the sum of every element in a list).

So the type of (foldr x) should be

Foldable t => b -> t a -> b

Ok but we're not done, let's see what happens with the use of map now.

map should first be given a function (by definition). The return value of (foldr x) is seen as that, which means that this use of map considers that (b -> ta -> b) is the type of the function that needs to be applied to every element of a list.

Maybe it's clearer written as (b -> (ta -> b)) . So, this use of map considers that it is given a function that takes some input of type b and returns a function that itself takes a foldable a and returns a b .

Ok we're almost there. Now, map still needs another argument: a list which elements are of the same type as the input of the function it will apply. So since the function we want to apply (the result of (foldr x) ) takes a b , our use of map will take a [b] .

So now we have :

 (a -> b -> b) -> [b] -> …

We're just lacking the type of the output value of that function composition, which is the type of the output value of this specific use of map. Since the function that is applied with map returns something of type (ta -> b) , then the list of thing we will obviously return will be of type [ta -> b] .

So in the end you have

Foldable t => (a -> b -> b) -> [b] -> [t a -> b]

as the type of map . foldr map . foldr .

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM