为什么函数组合有时需要两个“。”结合两个功能

时间:2019-08-09 13:48:12

标签: haskell functional-programming function-composition

这个问题很简单,但我似乎无法理解这个概念。

要编写普通功能,只需执行以下操作即可:

carList

但是,有时候,这有时行不通:

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

出现错误:

myConcatMap = concat . map

但是当这样表达相同的功能时:

<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]

它完全按预期工作。

我知道这是有原因的,但是我盯着它看了一阵子,但仍然不太明白为什么原版不起作用而这个原本可以起作用。

为什么有两个“。”的?

4 个答案:

答案 0 :(得分:13)

这很容易从(.)的定义和Haskell语法知识中得出。

首先要更明确地定义myConcatMap

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

通过组合运算符的定义,您可以将其编写为

\f -> concat . (map f)

使用.在前缀位置(而不是infix运算符)进行重写。

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

并添加一些多余的括号,因为函数应用是左关联的。

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

使用部分语法重新编写,以使.再次成为中缀运算符

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

并使用函数(.)(concat .)再次应用map的定义:

(concat .) . map

答案 1 :(得分:5)

这是因为map是一个两个参数的函数,您只想在提供两个参数后才应用concat。请记住,Haskell多参数函数是可咖喱的,即实际上是

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

因此,如果您编写合成c . map,则c的自变量必须为[a]->[b]类型。但是concat的参数应该是一个列表,即类型为[b]或实际上为[[e]]的类型。

解决方案:

  • 显式传递第一个参数。

    myConcatMap f = concat . map f
    

    之所以可行,是因为map f不再是一个单参数函数[a] -> [b],因此您可以在其前面编写concat

  • 在函数前面编写concat,这是将map应用于其第一个参数的结果。这就是您在示例中所做的。

答案 2 :(得分:2)

合成运算符(.)的类型为(a->b) -> (b->c) -> (a->c),这意味着它需要2个一元函数并将前者的输出转发给后者。

对于concat . mapmap函数是二进制的。其类型(a->b) -> [a] -> [b]不属于(b->c)类型的(.)部分。

好吧,它实际上是这样做的:(a->b)的{​​{1}}自变量进入map的{​​{1}},而b“剩余”进入(b->c) ,但是这会使类型检查器认为您实际上有一个函数列表并想对其进行操作。这实际上是可能的,但是存在与您最初提出的问题无关的类型问题,而且显然不是您想做什么。

您的代码可以通过以下方式重写:

[a] -> [b]

现在我们将c的参数与myConcatMap f = concat . map f 进行了比较,它变成了一元函数,并且构成良好。

答案 3 :(得分:1)

让我们看看一些类型签名。

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

现在,concat . map有意义吗?为简单起见,我们假设Foldable成员只是列表。

(.)的第一个参数是concat,类型为[[d]] -> [d](为了避免名称冲突)。将其替换为(b -> c)可得到:

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

尝试将其应用于map。应用于单个参数,map为您提供了一个功能;这与[[d]]期望其第一个参数的(.) concat不匹配。我们遇到了一个问题。

但是如果您首先为map提供一个参数怎么办? map g具有签名[e] -> [f],因此我们得到以下类型签名:

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

该类型检查,因此我们在这里有了一些有意义的东西!如果您会注意到,我们首先将map应用于g,然后将(.) concat(相当于(concat .))应用于该结果,以便可以像这样重写函数:

(concat .) . map $ g

这种形式使我们完全摆脱了g并将您的函数myConcatMap变成无点形式:

myConcatMap = (concat .) . map