我一直在努力去抓住Haskell中的(.)
和($)
运算符。我相信我很了解它们之间的差异,但我仍然很难用这些运算符成功替换表达式中的括号。
例如,如果我有表达式
print(show(take 5 [1..10]))
我了解如何使用(.)
和($)
运算符
print . show . take 5 $ [1..10]
但是,如果我有类似
的话print (show (take (snd (1,5)) [1..10]))
我可以将此简化为最远的是
print . show . take (snd (1,5)) $ [1..10]
无论我尝试什么,我似乎无法用($)
或(.)
运算符替换那些内括号并使其成功编译。我知道大多数Haskeller通常在括号上使用($)
和(.)
运算符,所以我也很努力地遵循这种风格。如果有人可以指出我如何在不使用括号的情况下重写上面的表达式,我认为这将有助于我使用那些更成功的运算符。感谢。
答案 0 :(得分:5)
你的重写...
print . show . take (snd (1,5)) $ [1..10]
......在可读性方面同样出色。事实上,以下建议会使其比必要的更复杂;但是,正如你所说,知道这些事情是可能的,可以更好地了解($)
和(.)
的工作方式。
应用于[1..10]
的第一个函数是......
take (snd (1,5))
...所以让我们暂时关注它。如果你试图在没有表达式的其余部分的情况下消除括号,你可能会最终得到......
take . snd $ (1,5)
要重建原始函数,我们需要将其应用于[1..10]
。这样做的一种自然方式是......
(take . snd $ (1,5)) [1..10]
...但由于我们避免使用括号,我们需要额外的诡计。有两个直接的选择。第一个是将列表传递给具有($)
部分的函数:
($ [1..10]) . take . snd $ (1,5)
就像(* 2)
是一个带数字并将它乘以2的函数一样,($ [1..10])
是一个函数......
GHCi> :t ($ [1..10])
($ [1..10]) :: (Num t, Enum t) => ([t] -> b) -> b
...接受一个函数并将其应用于[1..10]
。 (是的,我刚刚添加了一对括号;但是,它们并没有真正用于分组 - ($ [1..10])
只是flip ($) [1..10]
的替代语法 - 所以我说它们不算数:))
此时,我们可以用与原始版本相同的方式完成表达式:
print . show . ($ [1..10]) . take . snd $ (1,5)
另一个选择是编写整个函数pointfree(也就是说,没有明确地编写参数)。要做到这一点,让我们回到......
take . snd $ (1,5)
......暂时离开这对......
take . snd
take . snd
是一个函数......
GHCi> :t take . snd
take . snd :: (a1, Int) -> [a] -> [a]
...需要一对和一个列表并返回一个列表。但是,我们也可以将此类型读作:
take . snd :: (a1, Int) -> ([a] -> [a])
...也就是说,我们有一个函数,它接受一对并返回一个函数。如果我们将某个函数foo
与take . snd
组合在一起,以便我们得到foo . take . snd
,foo
将应用于take . snd
生成的函数。事实上,当我们在上面使用($ [1..10])
时,我们已经做到了这一点。除了提供论证之外,我们可以用函数做的另一件事是组合它们。我们确实有一个用于编写函数的运算符......
((print . show) .) . take . snd
...即(.)
。括号内的奇怪表达式是(.)
的一部分,就像我们之前用于($)
的部分一样,除了这是左部分,这是一个正确的部分。由于操作员优先权诡计需要额外的一对括号;同样令人不满意的替代方案包括......
(print .) . (show .) . take . snd
......和......
(.) print . (.) show . take . snd
有了我们手中的无点功能,我们可以用我们想要的方式提供参数 - 例如,像这样......
(((print . show) .) . take . snd) (1,5) [1..10]
......或者......
($ [1..10]) . ((print . show) .) . take . snd $ (1,5)
......甚至是......
($ [1..10]) . ($ (1,5)) $ ((print . show) .) . take . snd
...或者,对于一些不那么奇怪的事情:
let f = ((print . show) .) . take . snd in f (1,5) [1..10]
确实有很多替代方案 - 他们都会密谋证明你原来的重写是多么美好!
答案 1 :(得分:3)
您的最终简化很好,并且在f . g . h $ x
风格中非常易读。这种风格的要点并不是要消除括号(尽管它可以清理它们并有时消除它们),但作为一种习惯用语,一目了然地表明一系列功能正在应用于某些事物和那些功能是什么。
在高中代数中使用表达式时,有时您可以在简化时删除括号,因为您已经固定了一组固定的运算符并且具有固定的操作顺序。这就是为什么必须将括号括在高中代数中的原因。如果你只是坚持(.)
和($)
,我认为这是你能得到的最好的。
这不推荐使用,因为您的简化版本很好,人们将不得不花时间弄清楚新的运算符意味着什么好处,但我只是为了完整性而添加它
我们在Haskell而不是高中代数中,所以我们可以添加一个具有任何优先级的新运算符。它需要像($)
一样工作,但其优先级等于(.)
(因为它不能大于(.)
,它具有最高优先级。)
因此,要消除ghci
中所有括号(元组除外):
Prelude> let infixr 9 $$$; a $$$ b = a b
Prelude> print . show . take $$$ snd (1, 5) $ [1..10]
"[1,2,3,4,5]"
取消所有括号:
Prelude> let infixr 9 $$$; a $$$ b = a b
Prelude> let tuple :: a -> b -> (a, b); tuple = (,)
Prelude> print . show . take $$$ snd $$$ tuple 1 5 $ [1..10]
"[1,2,3,4,5]"