什么(f.g 在哈斯克尔中的意思



我已经看到很多函数是根据模式(f .) . g定义的。例如:

countWhere = (length .) . filter
duplicate  = (concat .) . replicate
concatMap  = (concat .) . map

这是什么意思?

点运算符(即 (.)(是函数组合算子。它的定义如下:

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,如下所示:

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:
(.:) = (.) . (.)

同样,我们得到:

(.::)  = (.) . (.) . (.)
(.:::) = (.) . (.) . (.) . (.)

正如你(.:)所看到的,(.::)(.:::)只是(.)的幂(即它们是(.)的迭代函数(。对于数学中的数字:

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,这非常整洁。

这是一个品味问题,但我觉得这种风格令人不快。 首先,我将描述它的含义,然后我建议一个我喜欢的替代方案。

您需要知道任何运算符? (f . g) x = f (g x)(f ?) x = f ? x。 由此我们可以推断出

countWhere p = ((length .) . filter) p
              = (length .) (filter p)
              = length . filter p

所以

countWhere p xs = length (filter p xs)

我更喜欢使用一个名为 .: 的函数

(.:) :: (r -> z) -> (a -> b -> r) -> a -> b -> z
(f .: g) x y = f (g x y)

然后countWhere = length .: filter. 就我个人而言,我觉得这要清楚得多。

(.:Data.Composition和其他地方也有定义。

相关内容

  • 没有找到相关文章

最新更新