为什么函数组合有时需要两个"."来组合两个函数



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

要组合普通函数,可以执行以下操作:

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]

但是当相同的函数表示为这样时:

myConcatMap = (concat .) . map

它完全按预期工作。

我知道这是有原因的,但是我已经盯着它看了一段时间,仍然不太明白为什么原版不起作用而这个却起作用了。

为什么有两个"."的?

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

您从更明确的myConcatMap定义开始,即

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

根据组合运算符的定义,您可以将其写为

f -> concat . (map f)

使用前缀位置的.而不是作为中缀运算符重写此内容。

f -> (.) concat (map f)

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

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

使用节语法重写此内容,使.再次成为中缀运算符

f -> (concat .) (map f)

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

(concat .) . map

这是因为map是一个双参数函数,并且您希望仅在提供两个参数后才应用concat。请记住,Haskell多参数函数是柯里化的,即它实际上是

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

因此,如果你写一个作文c . mapc的参数必须是[a]->[b]类型的东西。但是concat的参数应该是一个列表,即[b]类型或实际上[[e]]的东西。

解决 方案:

  • 显式传递第一个参数。

    myConcatMap f = concat . map f
    

    这是有效的map f因为它不再是一个单参数函数[a] -> [b],因此您可以在它前面撰写concat

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

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

concat . map的情况下,map函数是二进制的。它的类型(a->b) -> [a] -> [b]不适合(b->c)类型(.)部分。

好吧,它确实如此:(a->b)map的参数进入(b->c)b[a] -> [b]"剩余"进入c,但这会导致类型检查器认为您实际上有一个函数列表并想要对其进行操作。这实际上是可能的,但是有一个类型问题与你最初的问题无关,这显然不是你想做的。

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

myConcatMap f = concat . map f

现在我们已经用f探讨了(a->b)论证,它变成了一个一元函数,它组成得很好。

让我们看一些类型签名。

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

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

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

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

尝试将其应用于map。应用于单个参数,map为您提供一个函数;这与(.) concat对其第一个论点的期望[[d]]不符。我们自己遇到了一个问题。

但是,如果您先向map提供一个参数呢?map g有签名[e] -> [f],所以我们最终得到这种类型的签名:

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

那个类型检查,所以我们这里有一些有意义的东西!如果你会注意到,我们首先将map应用于g,然后将(.) concat(等效于(concat .)(应用于该结果,以便可以像这样重写该函数:

(concat .) . map $ g

这种形式允许我们完全摆脱g,并将您的函数myConcatMap变为无点形式:

myConcatMap = (concat .) . map

最新更新