GHC Proposal: Top-level IO initialized bindings
I love the goal of this proposal, but I don’t think it’s approaching the problem from quite the right angle.
Most of the problems and now additional complexities it brings are a result of having a top-level version of unsafePerformIO, but the unsafePerformIO bit is really incidental to the original motivation.
What this is really trying to do is come up with a way of sharing the result of a top-level IO computation. Stripping away the IO is currently the implementation for that because it works well for newIORef, but that’s very much not the case for arbitrary IO.
So what if instead of restricting it to a “safe” subset of IO, a %TopLevelIO declaration just kept the IO type and desugared into (something equivalent to) this
%TopLevelIO
blah :: IO Blurg
blah = someEffectfulFunction
~>
blah_impl :: Blurg
blah_impl = unsafePerformIO someEffectfulFunction
{-# OPAQUE blah_impl #-}
blah :: IO Blurg
blah = pure $! blah_impl
This way the only observable difference compared to not having the %TopLevelIO annotation would be that running the computation twice wouldn’t do anything, but we’re already in IO so that’s a perfectly acceptable behavior.
In order to do anything useful with the resulting IORef, you will have to be in IO anyway, so this shouldn’t lose much expressiveness (and if you really need the result in pure code, you can always use unsafePerformIO blah and deal with the consequences of that unsafePerformIO yourself)
Discussion in the ATmosphere