想出(.) . (.)
实际上非常简单,它所做的事情背后的直觉很难理解。
(.)
|
在将表达式重写为“管道”样式计算(想想在 shell 中)时让你走得很远。但是,一旦您尝试将一个接受多个参数的函数与一个只接受一个参数的函数组合在一起,使用起来就会变得很尴尬。例如,让我们定义concatMap
:
concatMap :: (a -> [b]) -> [a] -> [b]
concatMap f xs = concat (map f xs)
摆脱xs
只是一个标准的操作:
concatMap f = concat . map f
但是,没有摆脱f
. 这是由一个事实引起的,它map
需要两个参数,我们想应用concat
它的最终结果。
您当然可以应用一些无点技巧并摆脱困境(.)
:
concatMap f = (.) concat (map f)
concatMap f = (.) concat . map $ f
concatMap = (.) concat . map
concatMap = (concat .) . map
但是很可惜,这段代码的可读性几乎没有了。相反,我们引入了一个新的组合器,它完全符合我们的需要:将第二个函数应用于第一个函数的最终结果。
-- .: is fairly standard name for this combinator
(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(f .: g) x y = f (g x y)
concatMap = concat .: map
好吧,这就是动力。让我们开始点免费业务。
(.:) = \f g x y -> f (g x y)
= \f g x y -> f ((g x) y)
= \f g x y -> f . g x $ y
= \f g x -> f . g x
现在,有趣的部分来了。这是另一个在您遇到困难时通常会有所帮助的无点技巧:我们将.
其重写为前缀形式并尝试从那里继续。
= \f g x -> (.) f (g x)
= \f g x -> (.) f . g $ x
= \f g -> (.) f . g
= \f g -> (.) ((.) f) g
= \f -> (.) ((.) f)
= \f -> (.) . (.) $ f
= (.) . (.)
至于直觉,你应该阅读这篇非常好的文章。我将解释以下部分(.)
:
让我们再想想我们的组合器应该做什么:它应该应用于f
结果的结果(我g
一直在故意使用最终结果,当你完全应用时,这真的是你得到的 - 模统一类型变量与另一个函数类型 -g
函数,这里的结果只是g x
一些应用程序x
)。
应用于结果f
对我们意味着什么?好吧,一旦我们应用了某个值,我们就会把结果应用到它上面。听起来很熟悉:就是这样。g
g
f
(.)
result :: (b -> c) -> ((a -> b) -> (a -> c))
result = (.)
现在,事实证明这些组合器的组合(我们的单词)只是一个函数组合,即:
(.:) = result . result -- the result of result