最適化、厳密性、および不正確な例外は、少し扱いにくい場合があります。
上記の問題を再現する最も簡単な方法は、NOINLINE
onを使用する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
、ケース精査の外部でへの呼び出しをフロートアウトします。
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 バグは非常に興味深い読み物です。
GHC はここで怪しいことをしていると思うので、使用しないことをお勧めしevalaute
ます (代わりに、seq
直接使用してください)。これは、GHC が厳密に何をしているのかをもっと考える必要があります。
GHC HQ から判断を得るために、バグ レポートを提出しました。