我曾经见过很多函数都是按照 (f .) . g
这种模式定义的。例如:
countWhere = (length .) . filter
duplicate = (concat .) . replicate
concatMap = (concat .) . map
这是什么意思?
我曾经见过很多函数都是按照 (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
的函数(即将第一个函数应用于第二个函数的结果)。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
。他们更喜欢以 pointfree 风格编写 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
,这非常整洁。
(.) ^ i
的表达式不是良类型的,因此它们实际上不是有效的 Haskell 代码。 - Tom Ellis^
代替数字是可以的。尽管如此,我会将运算符更改为 .^
以区分两者。 - Aadit M ShahcountWhere f xs = length (filter f xs)
比其他所有版本都要可读得多,因为它能够立即清楚地表明:1) 函数有多少个参数,2) 它接受什么样的参数(通过“聪明”的名称),3) 几乎任何程序员都可以在几秒钟内阅读并有一些直觉上的了解所发生的事情。当表达式变得比简单的组合更复杂时,弄清点无表示法的版本需要更多的时间。 - Bakuriumf
函数就是这种情况的范例。尽管如此,点无风格编程很有趣,从数学角度研究它可以使程序的结构更具数学可处理性。它可以让你的代码更短,但只有在有限的情况下。 - Aadit M Shah这是一个关于品味的问题,但我觉得这种风格不太好看。首先我会解释一下它的含义,然后我会提出一个我更喜欢的替代方案。
你需要知道 (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
中被定义,可能也在其他地方被定义。)
(.:)
定义为 (.:) = fmap fmap fmap
。这更加通用,因为你可以用于任何函子。例如,你可以执行 (* 2) .: Just [1..5]
。当然,你需要给它正确的类型签名:(.:) :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
。 - Aadit M Shah<$$> = fmap . fmap
的写法,因为 .:
按照惯例被专门用于 (->) r
,而且 "outer" fmap
已经在 (->) r
函子上了。 - kqr
((f .). g)x y = f(g x y)
的意思是将函数f和g组合起来,并将输入x和y传递给它们。首先,将g应用于x和y,然后将结果传递给f。简而言之,这个等式描述了函数复合的过程。 - fghzxm