为什么乘法只在一侧短路

时间:2016-03-17 00:47:00

标签: haskell short-circuiting

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

在玩完它后,似乎原因是因为在两个方向上使它短路是非常重要的,因为如果没有某种奇怪的并行计算并且开始它并没有多大意义第一个非底部返回。

这种事情是否在其他地方出现(反身函数对于底值不反思),是否可以安全依赖?还有一种实用的方法可以使0(0 *)评估为零,无论传入的值如何。

3 个答案:

答案 0 :(得分:41)

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

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

答案 1 :(得分:7)

以下面的例子为例

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

您必须选择要评估的一方。如果expensiveTest2是一个无限循环,你永远无法判断右边是否0,所以你无法判断右边是否短路,所以你永远不会去看看左边。您无法一次检查双方是否0

至于你是否可以依靠短路来以某种方式行动,请记住,undefinederror行为正如那样无限循环因为你不使用IO。因此,您可以使用undefinederror来测试短路和懒惰。通常,短路行为因功能而异。 (也有不同程度的懒惰。undefinedJust undefined可能会产生不同的结果。)

有关详细信息,请参阅this

答案 2 :(得分:6)

实际上,似乎fix (* 0) == 0仅适用于Integer,如果您运行fix (* 0) :: Doublefix (* 0) :: Int,您仍会获得***Exception <<loop>>

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

timesIntegerData.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>