为什么乘法只在一侧短路
Why does multiplication only short circuit on one side
我在弄乱 fix
,弄乱之后我遇到了一些奇怪的行为,即 0 * undefined
是 *** Exception: Prelude.undefined
而 undefined * 0
是 0
。这也意味着 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
。
至于是否可以依靠短路以某种方式起作用,请记住 undefined
和 error
的行为 完全 就像只要不使用 IO,就会无限循环。因此,您可以使用 undefined
和 error
来测试短路和惰性。通常,短路行为因功能而异。 (也有不同程度的懒惰,undefined
和Just undefined
可能给出不同的结果。)
有关详细信息,请参阅 this。
实际上,fix (* 0) == 0
似乎只适用于Integer
,如果你运行 fix (* 0) :: Double
或fix (* 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>
我在弄乱 fix
,弄乱之后我遇到了一些奇怪的行为,即 0 * undefined
是 *** Exception: Prelude.undefined
而 undefined * 0
是 0
。这也意味着 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
。
至于是否可以依靠短路以某种方式起作用,请记住 undefined
和 error
的行为 完全 就像只要不使用 IO,就会无限循环。因此,您可以使用 undefined
和 error
来测试短路和惰性。通常,短路行为因功能而异。 (也有不同程度的懒惰,undefined
和Just undefined
可能给出不同的结果。)
有关详细信息,请参阅 this。
实际上,fix (* 0) == 0
似乎只适用于Integer
,如果你运行 fix (* 0) :: Double
或fix (* 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>