forsyde-deep-0.2.0: src/ForSyDe/Deep/OSharing.hs
-----------------------------------------------------------------------------
-- |
-- Module : ForSyDe.Deep.OSharing
-- Copyright : (c) ES Group, KTH/ICT/ES 2007-2013
-- License : BSD-style (see the file LICENSE)
--
-- Maintainer : forsyde-dev@ict.kth.se
-- Stability : experimental
-- Portability : portable
--
-- This module provides support for Observable Sharing: <http://www.cs.chalmers.se/~koen/pubs/entry-asian99-lava.html>
--
-- It provides:
--
-- * 'URef': Unsafe Unmutable references, using them causes side effects.
--
-- * 'URefTable': a table were 'URef's are used as key and can store
-- any value.
--
-- /This Module was taken from Lava2000's/ @Ref.hs@ /module/:
-- <http://www.cs.chalmers.se/~koen/Lava/>
--
-- IMPORTANT WARNING: Even if all the functions causing side effects
-- are set as NOINLINE and that all currently known
-- Haskell compilers are based in graph reduction
-- (i.e. referential transparency will be preserved if
-- sharing is), there are other optimisations than inlining
-- that can break Observable Sharing, e.g. Common
-- Subexpression Elimination (CSE).
-----------------------------------------------------------------------------
module ForSyDe.Deep.OSharing
( -- Unsafe references
URef,
newURef,
readURef,
-- Tables of Unsafe References (IO version)
URefTableIO,
newURefTableIO,
addEntryIO,
queryIO,
-- Tables of Unsafe References (IO version)
URefTableST,
newURefTableST,
addEntryST,
queryST,
-- Memoizating functions
memoURef,
memoURefIO,
memoURefST)
where
import ForSyDe.Deep.OSharing.UDynamic
import System.IO (fixIO)
import System.IO.Unsafe (unsafePerformIO)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Control.Monad.ST
import qualified Control.Monad.ST.Unsafe as STU
-- | An Unsafe Unmutable Reference to a value of type a
--
-- An 'URef' is implemented as follows: it has two pieces of information. The
-- first one is an updatable list of entries for each table it is a member in.
-- Since the types of the Tables vary, the URef has no idea what type of
-- values it is supposed to store. So we use dynamic types.
--
-- Since it is an updatable list, it is an IORef, which we also use to compare
-- two 'URef's. The second part is just the value the URef is pointing at (this
-- can never change anyway since references are unmutable).
--
-- FIXME: why IORef? it should maybe be STRef?
-- Besides: Is it guaranteed that the values of an IORef
-- are not changed by the garbage collector?
data URef a
= URef (IORef [(URefTableTag, UDynamic)]) a
instance Eq (URef a) where
URef r1 _ == URef r2 _ = r1 == r2
instance Show a => Show (URef a) where
showsPrec _ (URef _ a) = showChar '{' . shows a . showChar '}'
-- | Create a new Unsafe Unmutable Reference to a Haskell object
--
-- This operation, as explained in the Observable sharing paper, can cuase
-- side-effects, since the value returned (the 'URef') is not determined by
-- the arguments of the function (i.e. different calls to 'newURef' with
-- the same argument return different 'URef's).
newURef :: a -> URef a
newURef a = unsafePerformIO $
do r <- newIORef []
return (URef r a)
{-# NOINLINE newURef #-}
-- | Read the value pointed by the 'URef'.
readURef :: URef a -> a
readURef (URef _ a) = a
--------------
-- URefTableIO
--------------
-- | A unique identifier which univocally designates a table
type URefTableTag
= IORef ()
-- | A 'URefTable' a b
--
-- * Unsafe References to a value of type "a" (key of the table)
-- * Values of type "b" associated to each key
--
-- Here is how we implement Tables of URefs:
--
-- A Table is nothing but a unique tag, of type TableTag. TableTag can be
-- anything, as long as it is easy to create new ones, and we can compare them
-- for equality. (I chose IORef ()).
--
-- So how do we store URefs in a Table? We do not want the Tables keeping
-- track of their URefs (which would be disastrous when the table becomes big,
-- and we would not have any garbage collection).
--
-- Instead, every URef keeps track of the value it has in each table it is
-- in. This has the advantage that we have a constant lookup time (if the
-- number of Tables we are using is small), and we get garbage collection of
-- table entries for free.
newtype URefTableIO a b
= URefTableIO URefTableTag
deriving Eq
-- | Create a new table
newURefTableIO :: IO (URefTableIO a b)
newURefTableIO = URefTableIO `fmap` newIORef ()
-- | Query the value corresponding to an 'URef'
queryIO :: URefTableIO a b -> URef a -> IO (Maybe b)
queryIO (URefTableIO t) (URef r _) =
do list <- readIORef r
return (unsafeFromUDyn `fmap` lookup t list)
-- | Add an ('URef' a, b) pair entry to the table
addEntryIO :: URefTableIO a b -- ^ key of the entry
-> URef a
-> b -- ^ value of the entry
-> IO ()
addEntryIO (URefTableIO t) (URef r _) b =
do list <- readIORef r
writeIORef r ((t, unsafeToUDyn b) : filter ((/= t) . fst) list)
--------------
-- URefTableST
--------------
-- | 'ST' version of 'URefTableIO'
newtype URefTableST s a b
= URefTableST (URefTableIO a b)
deriving Eq
-- | 'ST' version of 'newURefTableIO'
newURefTableST :: ST s (URefTableST s a b)
newURefTableST = STU.unsafeIOToST (URefTableST `fmap` newURefTableIO)
-- | 'ST' version of 'queryIO'
queryST :: URefTableST s a b -> URef a -> ST s (Maybe b)
queryST (URefTableST tab) r = STU.unsafeIOToST (queryIO tab r)
-- | 'ST' version of 'addEntryIO'
addEntryST :: URefTableST s a b -> URef a -> b -> ST s ()
addEntryST (URefTableST tab) r b = STU.unsafeIOToST (addEntryIO tab r b)
--------------------------------
-- Memoization of URef functions
--------------------------------
-- | Generates a memoizated version of a function taking 'URef' values
memoURef :: (URef a -> b) -> (URef a -> b)
memoURef f = unsafePerformIO . memoURefIO (return . f)
{-# NOINLINE memoURef #-}
-- | 'IO' version of 'memoURef'
memoURefIO :: (URef a -> IO b) -> (URef a -> IO b)
memoURefIO f = unsafePerformIO $
do tab <- newURefTableIO
let f' r = do mb <- queryIO tab r
case mb of
Just b -> do return b
Nothing -> fixIO $ \b ->
do addEntryIO tab r b
f r
return f'
{-# NOINLINE memoURefIO #-}
-- | 'ST' version of 'memoURef'
memoURefST :: (URef a -> ST s b) -> (URef a -> ST s b)
memoURefST f = unsafePerformST $
do tab <- newURefTableST
let f' r = do mb <- queryST tab r
case mb of
Just b -> do return b
Nothing -> fixST $ \b ->
do addEntryST tab r b
f r
return f'
where unsafePerformST = unsafePerformIO . STU.unsafeSTToIO
{-# NOINLINE memoURefST #-}