-- |
-- Module:      Acme.RealWorld
-- Copyright:   (c) Joseph Adams 2011
-- Maintainer:  joeyadams3.14159@gmail.com
-- Portability: GHC-only
--
-- Primitives for manipulating the state of the universe.

{-# LANGUAGE MagicHash, UnboxedTuples #-}
module Acme.RealWorld (
    -- * The RealWorld type
    RealWorld,

    -- * Universe manipulation primitives
    getWorld,
    putWorld,
    execIO,

    -- * Derived combinators
    hypothetically,
) where

import qualified Control.Exception as E
import GHC.IO
import GHC.Exts

fromState :: State# RealWorld -> RealWorld
fromState = unsafeCoerce#

toState :: RealWorld -> State# RealWorld
toState = unsafeCoerce#

-- | Retrieve the current state of the universe.
getWorld :: IO RealWorld
getWorld = IO (\s -> (# s, fromState s #))

-- | Set the current state of the universe.  Program values are not affected by
-- this operation, but the rest of the universe is.
--
-- 'putWorld' may not be called on the same state twice (this is enforced by
-- the runtime system).  Otherwise, it would be possible to trap the universe
-- in a temporal loop:
--
-- >getWorld >>= forever . putWorld
putWorld :: RealWorld -> IO ()
putWorld s' = IO (\_ -> (# toState s', () #))

-- | Given an action, construct a function that, given a state of the universe,
-- returns the state of the universe after the action has occurred.
--
-- Example:
--
-- >main = do
-- >    let f = execIO $ putStrLn "Second"
-- >        g = execIO $ putStrLn "First"
-- >        getWorld >>= putWorld . f . g
execIO :: IO a -> RealWorld -> RealWorld
execIO (IO k) = \w -> case k (toState w) of (# s', _ #) -> fromState s'

-- | Perform an action and return its value, but undo any side effects to the
-- universe.  Thus, it appears to return instantly, regardless of how long the
-- action would take to run.
--
-- The caller must ensure that the program /would/ have enough time to perform
-- the computation.  Otherwise, either an exception will be thrown, or the
-- operation will block because it never gets a chance to restore the original
-- state of the universe.
hypothetically :: IO a -> IO a
hypothetically action = E.bracket getWorld putWorld (\_ -> action)