点运算符(即(.))是function composition 运算符。定义如下:
infixr 9 .
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)
如您所见,它接受b -> c 类型的函数和a -> b 类型的另一个函数并返回a -> c 类型的函数(即,它将第一个函数应用于第二个函数的结果)。
函数组合运算符非常有用。它允许您将一个函数的输出通过管道传输到另一个函数的输入。例如,您可以在 Haskell 中编写一个tac 程序,如下所示:
main = interact (\x -> unlines (reverse (lines x)))
不是很可读。但是,使用函数组合,您可以编写如下:
main = interact (unlines . reverse . lines)
正如您所见,函数组合非常有用,但您不能在任何地方都使用它。例如,您不能使用函数组合将filter 的输出通过管道传输到length:
countWhere = length . filter -- this is not allowed
不允许这样做的原因是filter 是(a -> Bool) -> [a] -> [a] 类型。将其与a -> b 进行比较,我们发现a 的类型为(a -> Bool),b 的类型为[a] -> [a]。这会导致类型不匹配,因为 Haskell 期望 length 的类型为 b -> c(即 ([a] -> [a]) -> c)。但它实际上是[a] -> Int 类型。
解决方法很简单:
countWhere f = length . filter f
但是,有些人不喜欢这种额外的悬空f。他们更喜欢将countWhere 写成pointfree 风格,如下所示:
countWhere = (length .) . filter
他们是怎么得到这个的?考虑:
countWhere f xs = length (filter f xs)
-- But `f x y` is `(f x) y`. Hence:
countWhere f xs = length ((filter f) xs)
-- But `\x -> f (g x)` is `f . g`. Hence:
countWhere f = length . (filter f)
-- But `f . g` is `(f .) g`. Hence:
countWhere f = (length .) (filter f)
-- But `\x -> f (g x)` is `f . g`. Hence:
countWhere = (length .) . filter
如您所见,(f .) . g 就是\x y -> f (g x y)。这个概念其实可以迭代:
f . g --> \x -> f (g x)
(f .) . g --> \x y -> f (g x y)
((f .) .) . g --> \x y z -> f (g x y z)
(((f .) .) .) . g --> \w x y z -> f (g w x y z)
它并不漂亮,但它完成了工作。给定两个函数,您还可以编写自己的函数组合运算符:
f .: g = (f .) . g
f .:: g = ((f .) .) . g
f .::: g = (((f .) .) .) . g
使用(.:) 运算符,您可以改写为countWhere,如下所示:
countWhere = length .: filter
有趣的是,尽管您也可以用无点样式写 (.:):
f .: g = (f .) . g
-- But `f . g` is `(.) f g`. Hence:
f .: g = (.) (f .) g
-- But `\x -> f x` is `f`. Hence:
(f .:) = (.) (f .)
-- But `(f .)` is `((.) f)`. Hence:
(f .:) = (.) ((.) f)
-- But `\x -> f (g x)` is `f . g`. Hence:
(.:) = (.) . (.)
同样我们得到:
(.::) = (.) . (.) . (.)
(.:::) = (.) . (.) . (.) . (.)
如您所见,(.:)、(.::) 和(.:::) 只是(.) 的幂(即它们是(.) 的iterated functions)。对于数学中的数字:
x ^ 0 = 1
x ^ n = x * x ^ (n - 1)
数学中的函数也是如此:
f .^ 0 = id
f .^ n = f . (f .^ (n - 1))
如果f 是(.) 那么:
(.) .^ 1 = (.)
(.) .^ 2 = (.:)
(.) .^ 3 = (.::)
(.) .^ 4 = (.:::)
这使我们接近本文的结尾。对于最后一个挑战,让我们以无点风格编写以下函数:
mf a b c = filter a (map b c)
mf a b c = filter a ((map b) c)
mf a b = filter a . (map b)
mf a b = (filter a .) (map b)
mf a = (filter a .) . map
mf a = (. map) (filter a .)
mf a = (. map) ((filter a) .)
mf a = (. map) ((.) (filter a))
mf a = ((. map) . (.)) (filter a)
mf = ((. map) . (.)) . filter
mf = (. map) . (.) . filter
我们可以进一步简化如下:
compose f g = (. f) . (.) . g
compose f g = ((. f) . (.)) . g
compose f g = (.) ((. f) . (.)) g
compose f = (.) ((. f) . (.))
compose f = (.) ((. (.)) (. f))
compose f = ((.) . (. (.))) (. f)
compose f = ((.) . (. (.))) (flip (.) f)
compose f = ((.) . (. (.))) ((flip (.)) f)
compose = ((.) . (. (.))) . (flip (.))
使用compose,您现在可以将mf 写为:
mf = compose map filter
是的,它有点难看,但它也是一个非常棒的令人难以置信的概念。您现在可以将\x y z -> f x (g y z) 形式的任何函数编写为compose f g,这非常简洁。