EvEff
EvEff copied to clipboard
Local state is not safe
E.g. the program:
import Control.Ev.Eff
main :: IO ()
main = print $ runEff $ local 0 $ do
localModify (+ 1)
localGet
Prints (when compiled with -O
):
0
I think that this is due to common sub-expression elimination. First the code above gets turned into something like:
import Control.Ev.Eff
main :: IO ()
main =
let !r = unsafePerformIO (newIORef 0)
!x1 = unsafePerformIO (readIORef r)
!() = unsafePerformIO (writeIORef r (x1 + 1)
!x2 = unsafePerformIO (readIORef r)
in pure x2
And common sub-expression elimination transforms that to:
import Control.Ev.Eff
main :: IO ()
main =
let !r = unsafePerformIO (newIORef 0)
!x1andx2 = unsafePerformIO (readIORef r)
!() = unsafePerformIO (writeIORef r (x1andx2 + 1)
in pure x1andx2
A simple solution seems to be to mark perform
NOINLINE
, but that probably affects performance.
I have a better solution, we can rewrite Ctl
into a monad transformer CtlM
which can run IO
actions properly. Then you only need one unsafePerformIO
at the top level in runCtl
.
I have been able to make it work, but I have to clean up my code a bit. Expect a pull request soon.
Update: the change significantly affects some benchmarks, so I am investigating further. But maybe the microbenchmarks are also not representative of real-world scenarios w.r.t. inlining and specialization as Alexis King's talk "Effects for Less" showed.
@noughtmare I tried to reproduce this with the current HEAD and it works as intended. Can your confirm that?
If I compile with optimizations then I can still reproduce it.