Why does multiplication only short circuit on one side

后端 未结 3 523
独厮守ぢ
独厮守ぢ 2021-02-03 17:39

I was messing around with fix and after messing around with it I came across some weird behavior, namely that 0 * undefined is *** Exception: Pre

相关标签:
3条回答
  • 2021-02-03 18:05

    Your reasoning is correct. There is an unamb package providing tools for the sort of parallel computation you refer to. In fact, it offers Data.Unamb.pmult, which tries, in parallel, to check whether each operand is 1 or 0, and if so immediately produces a result. This parallel approach is likely to be much slower in most cases for simple arithmetic!

    The short-circuiting of (*) occurs only in GHC version 7.10. It came about as a result of changes to the implementation of the Integer type in that GHC version. That extra laziness was generally seen as a performance bug (as it interferes with strictness analysis and can even lead to space leaks in theory), so it will be removed in GHC 8.0.

    0 讨论(0)
  • 2021-02-03 18:05

    Actually, it seems that fix (* 0) == 0 only works for Integer, if you run fix (* 0) :: Double or fix (* 0) :: Int, you still get ***Exception <<loop>>

    That's because in instance Num Integer, (*) is defined as (*) = timesInteger

    timesInteger is defined in 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#)))
    

    Look at the above code, if you run (* 0) x, then timesInteger _ (S# 0#) would match so that x would not be evaluated, while if you run (0 *) x, then when checking whether timesInteger _ (S# 0#) matches, x would be evaluated and cause infinite loop

    We can use below code to test it:

    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
    

    There is something even more interesting, in GHCI:

    *Test> let x = fix (* 0) 
    *Test> x 
    0
    *Test> x :: Double 
    *** Exception: <<loop>>
    *Test>  
    
    0 讨论(0)
  • 2021-02-03 18:25

    Take the following for example

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

    You have to choose a side to evaluate. If expensiveTest2 is an infinite loop, you will never be able to tell whether the right side is 0 or not, so you can't tell whether or not to short circuit the right side, so you never get to look at the left side. You can't check if both sides are 0 at once.

    As for whether you can rely on short circuiting to act in a certain way, just keep in mind that undefined and error acts exactly like an infinite loop as long as you don't use IO. Therefore, you can test short circuiting and laziness using undefined and error. In general, short circuiting behavior varies from function to function. (There are also different levels of laziness. undefined and Just undefined may give different results.)

    See this for more details.

    0 讨论(0)
提交回复
热议问题