Using unsafeInterleaveIO
, you can create data structures that with nondeterminstic side effects. For example, you can use traverse unsafeInterleaveIO :: [IO a] -> IO [a]
to create a list where side effects are performed when the list’s elements are evaluated.
However, this has the problem of requiring your data structure to be Traversable and in particular this means you can’t have functions with nondeterministic side effects.
Here’s a generalization of unsafeInterleaveIO
, that I discovered, which remedies this issue.
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
module Interleave where
import GHC.IO (IO (..))
unsafeInterleaveIOF :: (a -> IO b) -> IO (a -> b)
unsafeInterleaveIOF f = IO $ \s ->
let f' a | IO g <- f a, (# _, x #) <- g s = x
in (# s, f' #)
unsafeWeakPerformIO :: IO (IO a -> a)
unsafeWeakPerformIO = unsafeInterleaveIOF id
unsafeDupableInterleaveIO :: IO a -> IO a
unsafeDupableInterleaveIO io = (\f -> f ()) <$> unsafeInterleaveIOF (\_ -> io)
Some notes (following from discussion on the Functional Programming Discord) :
-
unsafeInterleaveIOF
is as memory safe asunsafeInterleaveIO
, as you could implement it by traversing usingunsafeInterleaveIO
on a memo trie. -
unsafeWeakPerformIO
is a weaker version ofunsafePerformIO
that doesn’t allow memory corruption. It prevents polymorphic references by being monomorphic. -
unsafeInterleaveIOF
andunsafeWeakPerformIO
are equivalent and can be implemented with each other.