我编写了一个用于评估给定数字的多项式的函数。多项式表示为系数列表(例如[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)
中评估的函数作为另一个函数的中间参数,需要三个参数。
答案 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对括号。