问题
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 composeconcat
in front of it.Compose
concat
in front of the function that's the result of applyingmap
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