如何使用函数组合(。)/ application($)运算符重写此表达式?

时间:2017-02-21 02:01:46

标签: haskell

我一直在努力去抓住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通常在括号上使用($)(.)运算符,所以我也很努力地遵循这种风格。如果有人可以指出我如何在不使用括号的情况下重写上面的表达式,我认为这将有助于我使用那些更成功的运算符。感谢。

2 个答案:

答案 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])

...也就是说,我们有一个函数,它接受一对并返回一个函数。如果我们将某个函数footake . snd组合在一起,以便我们得到foo . take . sndfoo将应用于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]"