優化,嚴格性和imprecise exceptions可能有點棘手。
重現上述這個問題的最簡單的方法是用一個NOINLINE
上throwIfNegative
(功能不被跨越模塊邊界任一內聯):
import Control.Exception
import Test.HUnit
throwIfNegative :: Int -> String
throwIfNegative n | n < 0 = error "negative"
| otherwise = "no worries"
{-# NOINLINE throwIfNegative #-}
case_negative =
handleJust errorCalls (const $ return()) $ do
evaluate $ throwIfNegative (-1)
assertFailure "must throw when given a negative number"
where errorCalls (ErrorCall _) = Just()
main = runTestTT $ TestCase case_negative
讀核心,以上時,內聯GHC優化evaluate
正確(?):
catch#
@()
@ SomeException
(\ _ ->
case throwIfNegative (I# (-1)) of _ -> ...
,然後浮出調用throwIfError
,案件scrutinizer之外:
lvl_sJb :: String
lvl_sJb = throwIfNegative lvl_sJc
lvl_sJc = I# (-1)
throwIfNegative =
\ (n_adO :: Int) ->
case n_adO of _ { I# x_aBb ->
case <# x_aBb 0 of _ {
False -> lvl_sCw; True -> error lvl_sCy
奇怪的是,在這一點上,現在沒有其他代碼現在調用lvl_sJb
,所以整個測試變成了死代碼,並被剝離出來 - GHC已經確定它沒有被使用!
使用seq
代替evaluate
是開心就好:
case_negative =
handleJust errorCalls (const $ return()) $ do
throwIfNegative (-1) `seq` assertFailure "must throw when given a negative number"
where errorCalls (ErrorCall _) = Just()
或爆炸模式:
case_negative =
handleJust errorCalls (const $ return()) $ do
let !x = throwIfNegative (-1)
assertFailure "must throw when given a negative number"
where errorCalls (ErrorCall _) = Just()
所以我認爲我們應該看看evaluate
語義:
-- | Forces its argument to be evaluated to weak head normal form when
-- the resultant 'IO' action is executed. It can be used to order
-- evaluation with respect to other 'IO' operations; its semantics are
-- given by
--
-- > evaluate x `seq` y ==> y
-- > evaluate x `catch` f ==> (return $! x) `catch` f
-- > evaluate x >>= f ==> (return $! x) >>= f
--
-- /Note:/ the first equation implies that @(evaluate x)@ is /not/ the
-- same as @(return $! x)@. A correct definition is
--
-- > evaluate x = (return $! x) >>= return
--
evaluate :: a -> IO a
evaluate a = IO $ \s -> let !va = a in (# s, va #) -- NB. see #2273
那#2273 bug是一個prett有趣的閱讀。
我認爲GHC在這裏做可疑的事情,並建議不要使用evalaute
(而是直接使用seq
)。這需要更多地考慮GHC在嚴格性方面正在做什麼。
我已經filed a bug report幫助從GHC總部得到一個決心。
我可以重現這一點。有趣!另外,如果在模塊中包含'throwIfNegative',標記爲'NOINLINE',則失敗。 – 2011-04-17 23:39:36