Map (+) [1,2,3] returns a list of functions?

I’m reading A Gentle Introduction to Haskell. Paraphrasing part of the book where it is discussing the usefulness of infix operators in the context of partial application.

(+) = \x y -> x+y essentially coerces an infix operator into an equivalent functional value and it is handy when passing an infix operator as an argument to a function, as in map (+) [1,2,3] (the reader should verify that this returns a list of functions!).

  • First, I don’t see that/how map (+) [1,2,3] returns a list of functions. My guess (which I guess is wrong) is that it will add 1, 2, & 3.

  • Second, I don’t know how to confirm it returns a list of functions. I tried it in GHCi and got an error


• No instance for (Show (Integer -> Integer))
        arising from a use of ‘print’
        (maybe you haven't applied a function to enough arguments?)
    • In a stmt of an interactive GHCi command: print it

Which indicates it is waiting for another argument. So I tried

> x = map (+) [1,2,3]
> x 2

But that is also an error

 • Couldn't match expected type ‘Integer -> t’
                  with actual type ‘[Integer -> Integer]’
    • The function ‘x’ is applied to one argument,
      but its type ‘[Integer -> Integer]’ has none
      In the expression: x 2
      In an equation for ‘it’: it = x 2
    • Relevant bindings include it :: t (bound at <interactive>:5:1)

In GHCi you could try

> :t map (+) [1, 2, 3]

to find its type.

3 Likes

Always remember that ghci is useful and interactive:

λ> :type +d map (+) [1,2,3]              -- +d makes things simpler to read
map (+) [1,2,3] :: [Integer -> Integer]

so yeah, map (+) [1,2,3] returns a list of functions (functions from integer to integer). This is because map f xs applies f to each element of xs, it does not merge them together (there is foldl and friends for that).

Does this help?

2 Likes

I recommend using :help in GHCi. There’s :t, such that you can do :t map (+) [1, 2, 3]. Try starting with the simple case of (+) 1 and map (+) [1].

2 Likes

All 3 of your suggestions are helpful.

My habitual way of debugging issues still isn’t to look at types first, but I guess in Haskell it should be.

> :t (+)
(+) :: Num a => a -> a -> a

> :t map (+) [1,2,3]
map (+) [1,2,3] :: Num a => [a -> a]

> :t +d map (+) [1,2,3]
map (+) [1,2,3] :: [Integer -> Integer]

So I see that (+) takes two Num's and returns a Num of the same type. In my case I’m using an array of Integers so using +d shows the more specific type.

I’m used to something like (a -> b) -> b where (a -> b) is a function that takes an a and returns a b, but I didn’t know that [a -> a] and [Integer -> Integer] means “list of functions (functions from integer to integer)” (or a to a), so that is helpful.

I can write a function

addIt :: Num a => p -> [a -> a]
addIt x = map (+) [1,2,3]

Which seems to be a valid function but I don’t know how to make use of it. It is waiting for another argument but I don’t know how to pass that in. I tried

a = addIt 1
b = addIt [1]
c = addIt (+1)

all product an error. An example of how to use it would be helpful.

Also, I have a question about the type signature of

addIt :: Num a => p -> [a -> a]

Is there a significance to the letter “p”? I also see “t” used sometimes and am guessing it has some meaning?

I’m going to first ignore “addIt x” because the unused x there is very distracting. I’m going to play with map (+) [1,2,3] first directly.

map (+) [1,2,3] = [(+) 1, (+) 2, (+) 3]

One thing I can do is to use that to make [(+) 1 10, (+) 2 10, (+) 3 10].

Do some algebra to verify for example

(+) 1 10 = (\f -> f 10) ((+) 1)

Therefore

  [(+) 1 10, (+) 2 10, (+) 3 10]
= map (\f -> f 10) [(+) 1, (+) 2, (+) 3]
= map (\f -> f 10) (map (+) [1,2,3])

If you want to get back to addIt x: It clearly doesn’t matter what x becomes, therefore all of these are fair game and in fact equal:

map (\f -> f 10) (addIt False)
map (\f -> f 10) (addIt "hello")
map (\f -> f 10) (addIt pi)

There is no significance in “p” or “t” or “a”.

4 Likes

I think I get it now.

My attempt at addIt [1,2,3] worked out to be [(+) 1, (+) 2, (+) 3] [1,2,3] which makes no sense.

So now what I take away from you explanation is that map (+) [1,2,3] creates a list of 3 partially applied functions [(+) 1, (+) 2, (+) 3], and you need a function such as \f -> f 10 as the first argument of another map operation to to apply the second argument to each partially applied function in the list. Since I can see how to do that in JavaScript I think I get it here as well.

If that sounds right then I’m good.

Thanks!

1 Like

Exactly!

To take it a step further you could use the function application operator ($) instead of \f -> f 10 (Prelude)

-- \f -> f 10 = ($ 10)

-- f $ x = ($) f x = (\g y -> g y) f x = f x

map ($ 10) (map (+) [1,2,3])

EDIT: note on section operators Section of an infix operator - HaskellWiki

2 Likes

Interesting to see how that works. Thanks!

A view remarks that you might find useful, although it sounds like you’ve got it now:

Since map (+) [1,2,3] :: [Integer -> Integer], it is a list of functions. As such, you could take the first element (using the function head) and then apply that function to an integer.

You could also do map (+1) [1,2,3], which would add 1 to each of the elements in the array (aka list).

Haskell never coerces types, so whenever you see an expression like map (+) [1,2,3], it’s possible to fully understand what it will do just by thinking through the types (and the meaning of (+) of course). This is why people think in a very type oriented way in Haskell, because they (the types, not the people) are very consistent.

1 Like

addIt is what is known as an applicative functor. A functor is a container like a list, and addIt is a list which contains functions.

 addIt (+) [1, 2, 3]       --   [(+) 1, (+) 2, (+) 3]

One way of using addIt is to use the <*> operator. This takes two lists, takes out the contents, does the calculation and puts the results back into a list. In fact, it applies all possible combinations.

 addIt <*> [1]              -- [2, 3, 4]
 addIt <*> [1, 1 1]       -- [2, 2, 2, 3, 3, 3, 4, 4, 4]
2 Likes

Since an applicative functor has been mentioned, I’d like to also point out that fmap is a more general map, applicable to all Functors (including [] lists) rather than lists only.

Prelude> :t map
map :: (a -> b) -> [a] -> [b]
Prelude> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b

The fmap also has an infix synonym <$>, which makes it easy to chain these common operations of first applying a function to a collection/bag (getting a collection of partially-applied functions), then applying more bags with values until the functions are fully applied:

Prelude> (+) <$> [1, 2, 3] <*> [10]
[11,12,13]
Prelude> (+) <$> [1, 2, 3] <*> [10, 20]
[11,21,12,22,13,23]
Prelude> (,,) <$> [1] <*> ['a'] <*> [True, False]
[(1,'a',True),(1,'a',False)]

Bonus: this one is a bit hard to figure out

Prelude> (<*>) <$> (((<$>) . (<$>)) (+) [[0], [1]]) <*> [[0], [10], [100]]
[[0],[10],[100],[1],[11],[101]]