Haskell 带排序和 (++) 的点运算符

Haskell dot operator with sort and (++)

我正在学习haskell,正在努力弄清楚前缀、中缀、优先级等的所有规则

在尝试实现附加两个列表并对它们进行排序的函数时,我开始使用:

appendAndSort :: [a] -> [a] -> [a]
appendAndSort = sort . (++)

不编译。

以下:

appendAndSort :: Ord a => [a] -> [a] -> [a]
appendAndSort = (sort .) . (++)

另一方面确实有效。

为什么我必须在排序处添加第二个点并在其周围添加括号?

表达式(f . g) x表示f (g x)

连贯地,(f . g) x y 表示 f (g x) y

请注意 y 如何作为第二个参数传递给 f,而不是 g。结果是不是f (g x y).

在你的例子中,(sort . (++)) x y 意味着 sort ((++) x) y,这将调用 sort 第一个参数 (++) x(在列表 x 前面添加的函数到它的列表参数),以及第二个参数 y。 las,这是 ill-typed 因为 sort 只接受一个参数。

因此,这也是无效的

appendAndSort x y = (sort . (++)) x y

故此

appendAndSort = sort . (++)

相比之下,((f .) . g) x y 确实按预期工作。让我们计算一下:

((f .) . g) x y
=  -- same reasoning as above, y is passed to (f.), not g
(f .) (g x) y
=  -- application associates on the left
((f .) (g x)) y
=  -- definition of `(f.)`
(f . (g x)) y
= -- definition of .
f ((g x) y)
=  -- application associates on the left
f (g x y)

所以这确实使 y 传递给 g(而不是 f)。

在我看来,“成语”(f .) . g 不值得使用。有针对性的 \x y -> f (g x y) 更易于阅读,而且不会太长。


如果您确实需要,可以定义一个自定义组合运算符来处理 two-argument 情况。

(.:) f g = \x y -> f (g x y)

然后,你可以这样写

appendAndSort = sort .: (++)

让我们从使用显式参数的版本开始。

appendAndSort x y = sort (x ++ y)

++ 写成前缀函数而不是运算符会得到

appendAndSort x y = sort ((++) x y)

知道(f . g) x == f (g x),我们可以识别f == sortg == (++) x得到

appendAndSort x y = (sort . (++) x) y

这让我们可以通过 eta conversion:

y 作为显式参数删除
appendAndSort x = sort . (++) x

接下来就是重复上面的过程,这次用(.)作为最上面的operator写成前缀函数,

appendAndSort x = (.) sort ((++) x)

然后用 f == (.) sortg == (++) 再次应用 . 的定义:

appendAndSort x = (((.) sort) . (++)) x

并通过 eta 转换消除 x

appendAndSort = ((.) sort) . (++)

最后一步是将 (.) sort 编写为运算符部分,我们完成了推导。

appendAndSort = (sort .) . (++)