---------------------------------------- -- | -- Module : Data.QuotientRef -- Copyright : (c) Luke Palmer 2008 -- License : BSD3 -- -- Maintainer : Luke Palmer -- Stability : experimental -- -- A Quotient Reference is a reference cell that needs two values to -- dereference. In a way it is a two-dimensional table indexed by references. -- The trick is that if a cell is indexed by @a@ and @b@, then if /either/ -- @a@ or @b@ gets cleaned up by the garbage collector, then so does the -- cell, because it would not be able to be accessed anymore. -- -- There are two different types of indices, "LeftRef" and "RightRef". You -- need one of each, of the same type, to access a cell. -- -- The name comes from the idea that the product of two indices is a reference, -- so each index is a quotient. -- -- Example usage: -- -- > do -- > l_1 <- newLeft -- > l_2 <- newLeft -- > r_1 <- newRight -- > r_2 <- newRight -- > write l_1 r_1 "Foo" -- > write l_2 r_1 "Bar" -- > print =<< read l_1 r_1 -- Just "Foo" -- > print =<< read l_1 r_2 -- Nothing -- > print =<< read l_2 r_1 -- Just "Bar" -- > print =<< read l_2 r_2 -- Nothing -- module Data.QuotientRef ( LeftRef, RightRef , newLeft, newRight , read, write ) where import Prelude hiding (read) import qualified Data.HashTable as Hash import Data.Unique import Control.Applicative import Control.Exception import Control.Monad import System.Mem.Weak -- | The left half of a reference cell. Combine this with a "RightRef" to -- access a cell. newtype LeftRef a = LeftRef (Hash.HashTable Unique a) -- RightRef is data rather than newtype, so we have something real to make -- a weak reference from. -- | The right half of a reference cell. Combine this with a "LeftRef" to -- access a cell. data RightRef a = RightRef Unique newLeft :: IO (LeftRef a) newLeft = LeftRef <$> Hash.new (==) (fromIntegral . hashUnique) newRight :: IO (RightRef a) newRight = RightRef <$> newUnique -- | Combine the two halves of a reference and return the result if -- it exists. read :: LeftRef a -> RightRef a -> IO (Maybe a) read (LeftRef hash) (RightRef ident) = do Hash.lookup hash ident -- | Combine the two halves of a reference and write a value to the -- product. write :: LeftRef a -> RightRef a -> a -> IO () write (LeftRef hash) right@(RightRef ident) val = block $ do existed <- Hash.update hash ident val unless existed $ do hashW <- mkWeakPtr hash Nothing addFinalizer right $ do h <- deRefWeak hashW case h of Nothing -> return () Just h' -> Hash.delete h' ident