我一直很努力把握(.)和($)运营商在哈斯克尔.我相信我很了解它们之间的差异,但我仍然很难用这些运算符成功替换表达式中的括号.
例如,如果我有表达式
print(show(take 5 [1..10]))
Run Code Online (Sandbox Code Playgroud)
我理解如何使用(.)和($)运算符重写它
print . show . take 5 $ [1..10]
Run Code Online (Sandbox Code Playgroud)
但是,如果我有类似的东西
print (show (take (snd (1,5)) [1..10]))
Run Code Online (Sandbox Code Playgroud)
最远的我可以将其简化为
print . show . take (snd (1,5)) $ [1..10]
Run Code Online (Sandbox Code Playgroud)
无论我尝试什么,我似乎无法用($)or (.)运算符替换那些内部括号,并让它成功编译.据我所知,大多数Haskeller通常在括号中使用($)和(.)运算符,所以我也很努力地遵循这种风格.如果有人可以指出我如何在不使用括号的情况下重写上面的表达式,我认为这将有助于我使用那些更成功的运算符.谢谢.
你的改写......
print . show . take (snd (1,5)) $ [1..10]
Run Code Online (Sandbox Code Playgroud)
......在可读性方面同样出色.事实上,以下建议会使其比必要的更复杂; 但是,正如你所说,知道这些事情是可能的,可以更好地理解($)和(.)工作.
应用的第一个功能[1..10]是......
take (snd (1,5))
Run Code Online (Sandbox Code Playgroud)
...所以让我们暂时关注它.如果你试图在没有表达式的其余部分的情况下消除括号,你最终可能会......
take . snd $ (1,5)
Run Code Online (Sandbox Code Playgroud)
要重建原始函数,我们需要将其应用于[1..10].这样做的一种自然方式是......
(take . snd $ (1,5)) [1..10]
Run Code Online (Sandbox Code Playgroud)
...但由于我们避免使用括号,我们需要额外的诡计.有两个直接的选择.第一个是通过一个($)部分将列表传递给函数:
($ [1..10]) . take . snd $ (1,5)
Run Code Online (Sandbox Code Playgroud)
就像(* 2)一个函数,它取一个数字并乘以它,($ [1..10])是一个函数...
GHCi> :t ($ [1..10])
($ [1..10]) :: (Num t, Enum t) => ([t] -> b) -> b
Run Code Online (Sandbox Code Playgroud)
...需要一个函数并将其应用于[1..10].(是的,我刚刚添加了一对括号;但是,它们并没有真正用于分组 - ($ [1..10])只是替代语法flip ($) [1..10]- 所以我说它们不算数:))
此时,我们可以用与原始版本相同的方式完成表达式:
print . show . ($ [1..10]) . take . snd $ (1,5)
Run Code Online (Sandbox Code Playgroud)
另一个选择是编写整个函数pointfree(也就是说,没有明确地编写参数).要做到这一点,让我们回到......
take . snd $ (1,5)
Run Code Online (Sandbox Code Playgroud)
......暂时离开这对:
take . snd
Run Code Online (Sandbox Code Playgroud)
take . snd 是一个功能......
GHCi> :t take . snd
take . snd :: (a1, Int) -> [a] -> [a]
Run Code Online (Sandbox Code Playgroud)
...需要一对和一个列表并返回一个列表.但是,我们也可以将此类型读作:
take . snd :: (a1, Int) -> ([a] -> [a])
Run Code Online (Sandbox Code Playgroud)
...也就是说,我们有一个函数,它接受一对并返回一个函数.如果我们撰写了一些功能,foo以take . snd使我们获得foo . take . snd,foo将被应用到所产生的作用take . snd.事实上,当我们($ [1..10])刚才使用时,我们已经做到了.除了提供论证之外,我们可以用函数做的另一件事是组合它们.我们确实有一个运算符来组合函数......
((print . show) .) . take . snd
Run Code Online (Sandbox Code Playgroud)
...即(.).括号内的奇怪表达式是一段(.),就像我们($)之前使用的那样,除了这是左侧部分,这是一个正确的部分.由于操作员优先权诡计需要额外的一对括号; 同样令人不满意的替代品包括......
(print .) . (show .) . take . snd
Run Code Online (Sandbox Code Playgroud)
......而且......
(.) print . (.) show . take . snd
Run Code Online (Sandbox Code Playgroud)
有了我们手中的无点功能,我们可以以我们想要的方式提供参数 - 例如,像这样......
(((print . show) .) . take . snd) (1,5) [1..10]
Run Code Online (Sandbox Code Playgroud)
... 或这个...
($ [1..10]) . ((print . show) .) . take . snd $ (1,5)
Run Code Online (Sandbox Code Playgroud)
......甚至这......
($ [1..10]) . ($ (1,5)) $ ((print . show) .) . take . snd
Run Code Online (Sandbox Code Playgroud)
...或者,对于不那么奇怪的事情:
let f = ((print . show) .) . take . snd in f (1,5) [1..10]
Run Code Online (Sandbox Code Playgroud)
确实有很多替代方案 - 而且它们都会合谋显示你原来的重写是多么美好!