14

Those are my first explorations in Haskell, so pardon me if it should be obvious.

I have been playing all afternoon with Haskell, sifting through the tutorial 99 questions on HaskellWiki, using my own list type (typical Cons). I've added "obvious" functions as I went on, and I have tried to make them as concise as possible (employing point-free notation whenever possible)

The 12th problem is about decoding a run-length encoded list, that is:

> decode [Multiple 5 'a', Single 'b', Multiple 2 'c']
"aaaaabcc"

And I thought about using map to decode each element, then concat the result (thanks Google on this), and finally remembered that I had seen something like concatMap in my readings, which GHCi quickly confirmed:

> :t map
map :: (a -> b) -> [a] -> [b]
> :t concat
concat :: [[a]] -> [a]
> :t concatMap
concatMap :: (a -> [b]) -> [a] -> [b]

It looked like it would be obvious to reimplement concatMap:

concatMap :: (a -> [b]) -> [a] -> [b]
concatMap = concat . map

Except that GHCi quite complains:

List.hs:110:15:
    Couldn't match expected type `[a] -> [b]'
                with actual type `[a0]'
    Expected type: [[a0]] -> [a] -> [b]
      Actual type: [[a0]] -> [[a0]]
    In the first argument of `(.)', namely `concat'
    In the expression: concat . map

I could not figure it out, so I looked up on the web, and the definition referenced in Prelude is actually:

concatMap f = concat . map f

And I don't quite understand why this f is necessary, since it's type is obviously a -> [b] as specified by the signature...

So why is f necessary here ?

Charles
  • 50,943
  • 13
  • 104
  • 142
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722

1 Answers1

15

Starting with the standard definition,

concat . map f 
 ≡ concat . (map f)
 ≡ \x -> concat ((map f) x)

Your first definition gives you:

(concat . map) f
 ≡ (\x -> concat (map x)) f
 ≡ concat (map f)

which doesn't type check because (map f) :: [a] -> [b], while concat takes [[a]], a list of lists.

Note the particular error message shown in the question doesn't describe the above type-check failure. The given message arises from declaring the return type concatMap as [a] -> [b], which isn't reconcilable with [a0], the return type of concat. If you leave off the type signature, the response is:

    Couldn't match type ‘[a1] -> [b]’ with ‘[[a]]’
    Expected type: (a1 -> b) -> [[a]]
      Actual type: (a1 -> b) -> [a1] -> [b]
    Relevant bindings include
      concatMap :: (a1 -> b) -> [a] (bound at :49:5)
    Probable cause: ‘map’ is applied to too few arguments
    In the second argument of ‘(.)’, namely ‘map’
    In the expression: concat . map

Here, the type error occurs when reconciling the return type of map with the argument type of concat. It turns out this case is more useful for debugging as it contains a hint why the type check failed.

outis
  • 75,655
  • 22
  • 151
  • 221
adamax
  • 3,835
  • 2
  • 19
  • 21
  • 1
    Ah! Is it fair to say that functions on the right-hand side of `.` should expect a single argument ? (not 2 like `map` before it was curried ?) Also, is there some trick to write this in point free style ? – Matthieu M. Apr 02 '11 at 17:40
  • 1
    @Matthiew Every function in Haskell accepts a single argument. For example, `map` accepts a function and returns a function `g = map f` which, in its turn, accepts a list and returns an element. Also, remember an important thing: function application binds tighter than any operator, it helps a lot to figure things out. I don't know how to write this point free. – adamax Apr 02 '11 at 17:46
  • it's definitely a strange journey I am embarking upon :) `replicateList :: Int -> List a -> List a` (Problem 15, altered) can be written point free as `replicateList = concatMap . replicate` and the two arguments are passed, no typechecking issue. – Matthieu M. Apr 02 '11 at 17:53
  • 2
    @Matthieu That's because it has a different order of application. If you write it with all arguments, then `replicateList n xs = concatMap (replicate n) xs`, but in your example `concatMap f xs = concat (map f xs)`. The 'bracket structure' is different. – adamax Apr 02 '11 at 18:02
  • Okay,I think I am going to run in a number of precedence issue before I got this right. The "write all arguments" will come in handy, thank you! – Matthieu M. Apr 02 '11 at 19:09
  • 1
    The answer as it stands is wrong: `(concat . map) f = concat (map f)` which doesn't typecheck since `map f` is not a list. – Ben Millwood Nov 05 '12 at 11:59
  • 1
    Point-free: `concatMap = (concat .) . map` – hdgarrood May 02 '13 at 20:55