Как насчет устранения источника проблемы - неявного эффекта - с помощью монады секвенирования, которая вводит этот эффект? Например. строгая идентификационная монада с отслеживанием:
data Eval a = Done a
| Trace String a
instance Monad Eval where
return x = Done x
Done x >>= k = k x
Trace s a >>= k = trace s (k a)
runEval :: Eval a -> a
runEval (Done x) = x
track = Trace
теперь мы можем писать вещи с гарантированным заказом trace
звонков:
main = print $ runEval $ do
t1 <- track "hit" 1
t2 <- track "hit" 1
return (t1 + t2)
все еще будучи чистым кодом, и GHC не будет пытаться добраться до умного, даже с -O2
:
$ ./A
hit
hit
2
Итак, мы вводим только эффект вычисления (отслеживание), достаточный для обучения GHC семантике, которую мы хотим.
Это чрезвычайно устойчиво для компиляции оптимизаций. Настолько, что GHC оптимизирует математику до 2
во время компиляции, но при этом сохраняет порядок операторов trace
.
В качестве доказательства того, насколько надежен этот подход, вот ядро с -O2
и агрессивным встраиванием:
main2 =
case Debug.Trace.trace string trace2 of
Done x -> case x of
I# i# -> $wshowSignedInt 0 i# []
Trace _ _ -> err
trace2 = Debug.Trace.trace string d
d :: Eval Int
d = Done n
n :: Int
n = I# 2
string :: [Char]
string = unpackCString# "hit"
Таким образом, GHC сделал все возможное для оптимизации кода, включая статическое вычисление математики, при этом сохраняя правильную трассировку.
Ссылки : полезная монада Eval
для секвенирования была введена Саймоном Марлоу .