0

the signature of map is (a -> b) -> [a] -> [b], which means that it takes 2 arguments and returns a list. Yet the following function, which transforms a string into a first letter capitalised clone is wrong:

modernise :: String -> String
modernise s = unwords . map (\m -> [toUpper (head m)] ++ tail m) (words s)

the good version is:

modernise :: String -> String
modernise = unwords . map (\m -> [toUpper (head m)] ++ tail m) . words

the first version is rejected with an error saying: "too many arguments for the map function"; but I gave 2 arguments (the lambda function and the result of words) which is the good number of arguments.

can you help me?

Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111
lolveley
  • 1,659
  • 2
  • 18
  • 34
  • 2
    I would prefer `map (\(x:xs) -> toUpper x : xs)` for the map function. Generally speaking, there's no reason to prefer `[x] ++ xs` over `x : xs`, and pattern matching should almost always be preferred over head and tail. – Rein Henrichs Oct 06 '15 at 16:47
  • @ReinHenrichs, indeed, and it's unfortunate that `words` returns a list of lists instead of a list of `NonEmpty`s. – dfeuer Oct 06 '15 at 17:08
  • Quick note for posterity: I closed this as a duplicate of question X, which is a duplicate of Y. However, I think there are some slight details different between X and Y that makes this question not a duplicate of Y; but the answers at X do indeed address the confusion arising in this question. (Probably X should not be a duplicate of Y in the first place, though I can see why it was closed that way.) – Daniel Wagner Oct 06 '15 at 20:31

3 Answers3

4

You want the application operator ($) :: (a -> b) -> a -> b instead of the dot which is function composition (.) :: (b -> c) -> (a -> b) -> a -> c.

modernise s = unwords $ map (\m -> [toUpper (head m)]  ++ tail m) (words s)

which is the same as

modernise s = unwords ( map (\m -> [toUpper (head m)]  ++ tail m) (words s) )
duplode
  • 33,731
  • 7
  • 79
  • 150
mb21
  • 34,845
  • 8
  • 116
  • 142
4

In the first version, you wrote

modernise s = unwords . map whatever (words s)

By the definition of ., this means

modernise s = \x -> unwords (map whatever (words s) x)

You could instead have used the $ operator:

modernise s = unwords $ map whatever (words s)

But the version you got to work is perfectly idiomatic and clear, so there's no need to change that aspect of its structure.

dfeuer
  • 48,079
  • 5
  • 63
  • 167
  • this is such an often made mistake that I wonder why GHC doesn't detect the intent behind it for new users and report an easier to understand error message. – Erik Kaplun Oct 06 '15 at 16:29
  • 2
    @ErikAllik, getting the type checker to give usable error messages is hard enough without trying to bake in knowledge of commonly confused functions. Furthermore, the type checker does not backtrack, and for the sake of speed is implemented using techniques that do not support backtracking. So it doesn't really have the ability to try using an alternative function hypothetically; as soon as it sees the `.` it commits to it. – dfeuer Oct 06 '15 at 17:16
2

"too many arguments for the map function"; but I gave 2 arrguments (the lambda function and the result of "words") which is the good number of arguments.

Exactly. In the subexpression map (\m -> [toUpper (head m)] ++ tail m) (words s) you gave map two arguments and arrived at a value of type [String] which is a non-function type. However you treat the result as a function, because the second argument to (.) must be of function type.

Whenever you try to supply a non-function value where a function is expected, the compiler provides diagnostic. The subexpression in question is a multiple application of function map, that's why the compiler makes a suggestion that you might have tried to feed it with too many arguments.

As others have already said, you need the ($) function.

ach
  • 2,314
  • 1
  • 13
  • 23