我看到很多功能是根据模式(f .) . g
定义的。例如:什么是(f。)。 g的意思是在Haskell中?
countWhere = (length .) . filter
duplicate = (concat .) . replicate
concatMap = (concat .) . map
这是什么意思?
我看到很多功能是根据模式(f .) . g
定义的。例如:什么是(f。)。 g的意思是在Haskell中?
countWhere = (length .) . filter
duplicate = (concat .) . replicate
concatMap = (concat .) . map
这是什么意思?
点运算符(即(.)
)是function composition运算符。它被定义为如下:
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)))
不是很易读:举例如下,你可以写在Haskell一个tac程序。使用功能组成但是你如下可以写它:
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:
(.:) = (.) . (.)
同样,我们得到:
(.::) = (.) . (.) . (.)
(.:::) = (.) . (.) . (.) . (.)
正如你可以看到(.:)
,(.::)
和(.:::)
都只是(.)
权力(即他们是的(.)
)。对于数字数学:
x^0 = 1
x^n = x * x^(n - 1)
同样的功能在数学:
f .^ 0 = id
f .^ n = f . (f .^ (n - 1))
如果f
是(.)
则:
(.) .^ 1 = (.)
(.) .^ 2 = (.:)
(.) .^ 3 = (.::)
(.) .^ 4 = (.:::)
这使我们接近这个文章的末尾。对于最后的挑战,让我们写pointfree风格以下功能:
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。 –
是的。然而,我确实写了“对数学中的函数类似”,因为这是一个数学解释,我认为使用'^'代替数字函数是可以的。不过,我会将操作符更改为'。^'来区分这两者。 –
我也很惊讶地发现数学中还有(。)^ i'。也许这种事物的正式框架存在于依赖型理论中。这将是有趣的。 –
这是一个味道的问题,但我觉得这样的风格是不愉快的。首先我会描述它的意思,然后我建议一个我更喜欢的替代方案。
对于任何运营商?
,您需要知道(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)。:只是[1..5]'。当然,你需要给它正确的类型签名'(。:) :) ::(Functor f,Functor g)=>(a - > b) - > f(g a) - > f(g b)'。 –
@AaditMShah在这种情况下,我宁愿像'<$$> = fmap。因为'(。:)'按照惯例专门用于'( - >)r',而“外部”'fmap'则在'( - >)r'仿函数上。 – kqr
(F)。g也可能是原作者代码中很好伪装的观点的一部分。 – Marton
我不确定这意味着什么。 –
这意味着作者正在聪明,并最终编写不可读的代码。 ;) – tibbe