Haskell 与零相乘时的延迟计算

Haskell 与零相乘时的延迟计算,haskell,infinite-loop,lazy-evaluation,Haskell,Infinite Loop,Lazy Evaluation,通过下面的程序 f 0 0 0 1 = 0 f 0 0 1 0 = f 0 0 0 1 + 1 f 0 1 0 0 = f 0 0 1 1 + 1 f 1 0 0 0 = f 0 1 1 1 + 1 f a b c d = (p + q + r + s) / (a + b + c + d) where p | a > 0 = a * f (a - 1) (b + 1) (c + 1) (d + 1) | otherwise = 0 q

通过下面的程序

f 0 0 0 1 = 0
f 0 0 1 0 = f 0 0 0 1 + 1
f 0 1 0 0 = f 0 0 1 1 + 1
f 1 0 0 0 = f 0 1 1 1 + 1
f a b c d = (p + q + r + s) / (a + b + c + d)
    where
    p
        | a > 0 = a * f (a - 1) (b + 1) (c + 1) (d + 1)
        | otherwise = 0
    q
        | b > 0 = b * f a (b - 1) (c + 1) (d + 1)
        | otherwise = 0
    r
        | c > 0 = c * f a b (c - 1) (d + 1)
        | otherwise = 0
    s
        | d > 0 = d * f a b c (d - 1)
        | otherwise = 0

main = print (f 1 1 1 1)
我想可以简化为

f 0 0 0 1 = 0
f 0 0 1 0 = f 0 0 0 1 + 1
f 0 1 0 0 = f 0 0 1 1 + 1
f 1 0 0 0 = f 0 1 1 1 + 1
f a b c d = (p + q + r + s) / (a + b + c + d)
    where
    p = a * f (a - 1) (b + 1) (c + 1) (d + 1)
    q = b * f a (b - 1) (c + 1) (d + 1)
    r = c * f a b (c - 1) (d + 1)
    s = d * f a b c (d - 1)

main = print (f 1 1 1 1)

因为除了两者在数学上都是合理的之外,我认为通过延迟求值,编译器或解释器应该能够决定将任何值乘以0都是不必要的。但是,这个程序确实进入了无限循环。为什么会这样?

内置乘法在两个参数中都是严格的——也就是说,它计算两个参数——不管其中一个参数是否为零,这就是导致程序循环的原因。您可以定义自己的乘法运算符,该运算符惰性地消除一个或另一个参数:

0 .* y = 0
x .* y = x * y
或者反过来说。定义一个两边都消除零的运算符需要更多的时间,但可以通过包来完成:


尽管,据我所知,这还没有一个足够可靠的实现:-/.

以及@luqui建议的定义自己的乘法运算符,您可以定义自己的类型,其内置乘法短路:

newtype SCZero a = SCZero a
    deriving Eq

instance Show a => Show (SCZero a) where
    show (SCZero x) = show x

instance (Eq a, Num a) => Num (SCZero a) where
    SCZero x + SCZero y = SCZero (x + y)
    SCZero 0 * SCZero y = SCZero 0
    SCZero x * SCZero y = SCZero (x * y)
    abs (SCZero x) = SCZero (abs x)
    signum (SCZero x) = SCZero (signum x)
    fromInteger x = SCZero (fromInteger x)
    negate (SCZero x) = SCZero (negate x)

instance (Eq a, Fractional a) => Fractional (SCZero a) where
    fromRational x = SCZero (fromRational x)
    SCZero 0 / SCZero y = SCZero 0
    SCZero x / SCZero y = SCZero (x / y) 
然后,您可以直接使用现有代码,只需将结果类型指定为
SCZero

*Main> print (f 1 1 1 1 :: SCZero Double)
0.464398781601087

@卡斯滕很好,因为从数学上讲,将任何值乘以0都是不必要的。也许我太相信Haskell编译器了?顺便说一句:我想你无论如何都会改变函数,例如
f0(-1)
在你的transformation@CarstenKönig有一些内置的规则可以避免乘以常数0。参数仍在计算中,只有乘法未执行。
*Main> print (f 1 1 1 1 :: SCZero Double)
0.464398781601087