Why function composition sometimes requires two “.” 's to combine two functions

霸气de小男生 提交于 2019-12-22 05:59:30

问题


So this question is simple but I can't seem to grasp the concept.

To compose ordinary functions, one can just do something like below:

lowerNoSpaces = filter (/= ' ') . map toLower

But, on occasion, there are times where this won't work:

myConcatMap = concat . map

It gives the error:

<interactive>:236:1: error:
    * Non type-variable argument
        in the constraint: Foldable ((->) [a1])
      (Use FlexibleContexts to permit this)
    * When checking the inferred type
        concattMap :: forall a1 a2.
                      Foldable ((->) [a1]) =>
                      (a1 -> a2) -> [a2]

But when the same function is expressed like this:

myConcatMap = (concat .) . map

It works exactly as intended.

I know there is a reason for this, but I have been staring at it for a while and still don't quite understand why the original doesn't work and this one does.

Why is there two "." 's?


回答1:


This is fairly easy to derive from the definition of (.) and knowledge of Haskell syntax.

You start with a more explicit definition of myConcatMap, which is

\f -> \xs -> concat (map f xs)

By definition of the composition operator, you can write this as

\f -> concat . (map f)

Rewrite this using . in prefix position rather than as an infix operator.

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

and add some redundant parentheses since function application is left-associative.

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

Rewrite this using section syntax to make . an infix operator again

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

and apply the definition of (.) one more time, using the functions (concat .) and map:

(concat .) . map



回答2:


It's because map is a two-argument function, and you want to apply concat only after both arguments have been supplied. Keep in mind that Haskell multi-argument functions are curried, i.e. it's actually

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

Thus, if you write a composition c . map, the argument of c must be something of type [a]->[b]. But the argument of concat is supposed to be a list, i.e. something of type [b] or actually [[e]].

Solutions:

  • Pass the first argument explicitly.

    myConcatMap f = concat . map f
    

    This works because map f is only a one-argument function anymore [a] -> [b], thus you can compose concat in front of it.

  • Compose concat in front of the function that's the result of applying map to its first argument. That's what you're doing in your example.




回答3:


The type of composition operator (.) is (a->b) -> (b->c) -> (a->c), which means that it takes 2 unary functions and forwards former's output to the latter.

In case of concat . map, the map function is binary. Its type (a->b) -> [a] -> [b] doesn't fit into (b->c) part of (.) type.

Well, it actually does: (a->b) argument of map goes into b of (b->c) and [a] -> [b] "leftover" goes into c, but that causes type checker to think that you actually have a list of functions and want to operate on it. This is actually possible, but there is a type problem unrelated to your initial question and it is clearly not what you wanted to do.

Your code can be rewritten this way:

myConcatMap f = concat . map f

Now we've plumbed (a->b) argument with f, it became an unary function, which composes well.




回答4:


Let's look at some type signatures.

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

Now, does concat . map make sense? For simplicity, let's assume that Foldable member is just lists.

The first argument to (.) is concat, of type [[d]] -> [d] (to avoid name collisions). Substituting that for (b -> c) gives us:

(.) concat :: (a -> [[d]]) -> a -> [d]

Try applying that to map. Applied to a single argument, map gives you a function; this does not match the [[d]] that (.) concat expects of its first argument. We've got ourselves an issue.

But what if you supply map with a single argument first? map g has signature [e] -> [f], so we end up with this type signature:

(.) concat (map g) :: [e] -> f

That typechecks, so we've got something meaningful here! If you'll notice, we're first applying map to g, then applying (.) concat (equivalently (concat .)) to that result, so that function can be rewritten like so:

(concat .) . map $ g

This form allows us to get rid of the g entirely and put your function myConcatMap into pointfree form:

myConcatMap = (concat .) . map


来源:https://stackoverflow.com/questions/57431487/why-function-composition-sometimes-requires-two-s-to-combine-two-functions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!