为什么乘法只在一侧短路

Why does multiplication only short circuit on one side

我在弄乱 fix,弄乱之后我遇到了一些奇怪的行为,即 0 * undefined*** Exception: Prelude.undefinedundefined * 00。这也意味着 fix (0 *)*** Exception: <<loop>>fix (* 0)0.

在玩弄之后,原因似乎是因为让它在两个方向上都短路是很重要的,因为这没有多大意义,没有某种奇怪的并行计算并开始返回第一个非底部。

这种东西在其他地方看到过吗(对底值不自反的自反函数),是我可以放心依赖的吗?还有一种实用的方法可以使 (0 *)(* 0) 计算为零,而不管传入的值如何。

你的推理是正确的。有一个 unamb 包为您所指的那种并行计算提供工具。事实上,它提供了 Data.Unamb.pmult,它会并行地尝试检查每个操作数是 1 还是 0,如果是,则立即产生结果。对于简单的算术运算,这种并行方法在大多数情况下可能会慢得多!

(*) 的短路 仅在 GHC 版本 7.10 中发生。它是由于该 GHC 版本中 Integer 类型的实现发生变化而产生的。这种额外的懒惰通常被视为 性能错误 (因为它会干扰严格性分析,甚至可能导致 space 理论上的泄漏),因此它将在 GHC 8.0 中被删除.

以下面为例

(if expensiveTest1 then 0 else 2) * (if expensiveTest2 then 0 else 2)

您必须选择一方进行评估。如果expensiveTest2是无限循环,你永远分不清右边是不是0,所以你分不清右边要不要短路,所以你永远看看左边。您不能同时检查双方是否 0

至于是否可以依靠短路以某种方式起作用,请记住 undefinederror 的行为 完全 就像只要不使用 IO,就会无限循环。因此,您可以使用 undefinederror 来测试短路和惰性。通常,短路行为因功能而异。 (也有不同程度的懒惰,undefinedJust undefined可能给出不同的结果。)

有关详细信息,请参阅 this

实际上,fix (* 0) == 0似乎只适用于Integer,如果你运行 fix (* 0) :: Doublefix (* 0) :: Int,你仍然得到***Exception <<loop>>

那是因为在instance Num Integer中,(*)被定义为(*) = timesInteger

timesInteger 定义在 Data.Integer

-- | Multiply two 'Integer's
timesInteger :: Integer -> Integer -> Integer
timesInteger _       (S# 0#) = S# 0#
timesInteger (S# 0#) _       = S# 0#
timesInteger x       (S# 1#) = x
timesInteger (S# 1#) y       = y
timesInteger x      (S# -1#) = negateInteger x
timesInteger (S# -1#) y      = negateInteger y
timesInteger (S# x#) (S# y#)
  = case mulIntMayOflo# x# y# of
    0# -> S# (x# *# y#)
    _  -> timesInt2Integer x# y#
timesInteger x@(S# _) y      = timesInteger y x
-- no S# as first arg from here on
timesInteger (Jp# x) (Jp# y) = Jp# (timesBigNat x y)
timesInteger (Jp# x) (Jn# y) = Jn# (timesBigNat x y)
timesInteger (Jp# x) (S# y#)
  | isTrue# (y# >=# 0#) = Jp# (timesBigNatWord x (int2Word# y#))
  | True       = Jn# (timesBigNatWord x (int2Word# (negateInt# y#)))
timesInteger (Jn# x) (Jn# y) = Jp# (timesBigNat x y)
timesInteger (Jn# x) (Jp# y) = Jn# (timesBigNat x y)
timesInteger (Jn# x) (S# y#)
  | isTrue# (y# >=# 0#) = Jn# (timesBigNatWord x (int2Word# y#))
  | True       = Jp# (timesBigNatWord x (int2Word# (negateInt# y#)))

看上面的代码,如果你运行 (* 0) x,那么timesInteger _ (S# 0#)会匹配,所以x不会被计算,而如果你运行 (0 *) x,那么在检查timesInteger _ (S# 0#)是否匹配的时候,x会被求值导致死循环

我们可以使用下面的代码进行测试:

module Test where
import Data.Function(fix)

-- fix (0 ~*) == 0
-- fix (~* 0) == ***Exception<<loop>>
(~*) :: (Num a, Eq a) => a -> a -> a
0 ~* _ = 0
_ ~* 0 = 0
x ~* y = x ~* y

-- fix (0 *~) == ***Exception<<loop>>
-- fix (*~ 0) == 0
(*~) :: (Num a, Eq a) => a -> a -> a
_ *~ 0 = 0
0 *~ _ = 0
x *~ y = x *~ y

在 GHCI 中还有更有趣的东西:

*Test> let x = fix (* 0) 
*Test> x 
0
*Test> x :: Double 
*** Exception: <<loop>>
*Test>