摆脱不必要的括号

时间:2016-07-07 15:35:19

标签: haskell coding-style parentheses

我编写了一个用于评估给定数字的多项式的函数。多项式表示为系数列表(例如[1,2,3]对应于x^2+2x+3)。

polyEval x p = sum (zipWith (*) (iterate (*x) 1) (reverse p))

如您所见,我首先使用了很多括号来分组应该评估的表达式。为了更好的可读性,我尝试使用.$消除尽可能多的括号。 (在我看来,两对以上的嵌套括号使得代码越来越难以阅读。)我知道函数应用程序具有最高优先级并且是左关联的。 .$都是正确关联的,但.的优先级为9,而$优先级为0.

所以在我看来,下面的表达式不能用更少的括号写出

polyEval x p = sum $ zipWith (*) (iterate (*x) 1) $ reverse p

我知道我们需要(*)(*x)的括号将它们转换为前缀函数,但有可能以某种方式删除iterate (*x) 1周围的括号吗?

您更喜欢哪种版本的可读性?

我知道还有很多其他方法可以实现相同的目标,但是我想讨论我的特定示例,因为它有一个在两个参数(iterate (*x) 1)中评估的函数作为另一个函数的中间参数,需要三个参数。

3 个答案:

答案 0 :(得分:2)

与往常一样,我更喜欢OP版本到目前为止提出的任何替代方案。我会写

polyEval x p = sum $ zipWith (*) (iterate (* x) 1) (reverse p)

然后离开它。 zipWith (*)的两个参数以与*的两个参数相同的方式扮演对称角色,因此eta-reducing只是混淆。

$的值是它使计算的最外层结构清晰:一个点上多项式的求值是某事物的总和。消除括号本身不应成为目标。

答案 1 :(得分:1)

所以它可能有点幼稚,但我真的很想从食物方面考虑Haskell的规则。我认为Haskell的左关联函数应用程序f x y = (f x) y是一种激进的nom greedy nom ,因为函数f拒绝等待让y到来并立即吃掉f,除非你花时间将这些东西放在括号中以制作一种“三明治”三明治" f (x y)(此时x会被吃掉,变得饥饿并吃掉y。)唯一的界限是运算符和特殊形式。

然后在特殊形式的边界内,操作员消耗周围的东西;最后,特殊形式花时间消化周围的表达。这是.$能够保存一些括号的唯一原因。

最后我们可以看到iterate (* x) 1可能会需要在三明治中,因为我们不想吃点东西iterate并停止。所以没有改变代码就没有好办法,除非我们能以某种方式取消zipWith的第三个参数 - 但是这个参数包含p所以需要写一些更多的东西点免费。

所以,一个解决方案是改变你的方法!将多项式存储为已反转方向的系数列表更有意义,因此x^2 + 2 * x + 3示例存储为[3, 2, 1]。然后我们不需要执行这个复杂的reverse操作。它还使得数学变得更简单,因为两个多项式的乘积可以递归地重写为(a + x * P(x)) * (b + x * Q(x)),这给出了简单的算法:

 newtype Poly f = Poly [f] deriving (Eq, Show)

 instance Num f => Num (Poly f) where
     fromInteger n = Poly [fromInteger n]
     negate (Poly ps) = Poly (map negate ps)
     Poly f + Poly g = Poly $ summing f g where
         summing [] g = g
         summing f [] = f
         summing (x:xs) (y:ys) = (x + y) : summing xs ys
     Poly (x : xs) * Poly (y : ys) = prefix (x*y) (y_p + x_q) + r where
         y_p = Poly $ map (y *) xs
         x_q = Poly $ map (x *) ys
         prefix n (Poly m) = Poly (n : m)
         r = prefix 0 . prefix 0 $ Poly xs * Poly ys

然后你的功能

evaluatePoly :: Num f => Poly f -> f -> f
evaluatePoly (Poly p) x = eval p where
    eval = (sum .) . zipWith (*) $ iterate (x *) 1

iterate周围缺少括号,因为eval是以无点样式编写的,因此可以使用$来使用表达式的其余部分。正如你所看到的那样,遗憾的是在(sum .)周围留下了一些新的括号来做这件事,所以它可能不值得你这么做。我发现后者的可读性低于比如说

evaluatePoly (Poly coeffs) x = sum $ zipWith (*) powersOfX coeffs where
    powersOfX = iterate (x *) 1

我甚至可能更喜欢写后者,如果高权力的表现不是超级关键,如powersOfX = [x^n | n <- [0..]]powersOfX = map (x^) [0..],但我认为iterate并不难理解总的来说。

答案 2 :(得分:0)

或许将其分解为更基本的功能将进一步简化。首先定义一个点积函数来乘以两个数组(内积)。

dot x y = sum $ zipWith (*) x y

并更改polyEval中的术语顺序以最小化括号

polyEval x p = dot (reverse p) $ iterate (* x) 1

减少到3对括号。