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 == sort
和g == (++) x
得到
appendAndSort x y = (sort . (++) x) y
这让我们可以通过 eta conversion:
将 y
作为显式参数删除
appendAndSort x = sort . (++) x
接下来就是重复上面的过程,这次用(.)
作为最上面的operator写成前缀函数,
appendAndSort x = (.) sort ((++) x)
然后用 f == (.) sort
和 g == (++)
再次应用 .
的定义:
appendAndSort x = (((.) sort) . (++)) x
并通过 eta 转换消除 x
appendAndSort = ((.) sort) . (++)
最后一步是将 (.) sort
编写为运算符部分,我们完成了推导。
appendAndSort = (sort .) . (++)
我正在学习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 == sort
和g == (++) x
得到
appendAndSort x y = (sort . (++) x) y
这让我们可以通过 eta conversion:
将y
作为显式参数删除
appendAndSort x = sort . (++) x
接下来就是重复上面的过程,这次用(.)
作为最上面的operator写成前缀函数,
appendAndSort x = (.) sort ((++) x)
然后用 f == (.) sort
和 g == (++)
再次应用 .
的定义:
appendAndSort x = (((.) sort) . (++)) x
并通过 eta 转换消除 x
appendAndSort = ((.) sort) . (++)
最后一步是将 (.) sort
编写为运算符部分,我们完成了推导。
appendAndSort = (sort .) . (++)