-----------------------------------------------------------------------------
-- |
-- Module : Data.Array.Diff
-- Copyright : (c) The University of Glasgow 2001
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : libraries@haskell.org
-- Stability : experimental
-- Portability : non-portable (uses Data.Array.IArray)
--
-- Functional arrays with constant-time update.
--
-----------------------------------------------------------------------------
module Data.Array.Diff (
-- * Diff array types
-- | Diff arrays have an immutable interface, but rely on internal
-- updates in place to provide fast functional update operator
-- '//'.
--
-- When the '//' operator is applied to a diff array, its contents
-- are physically updated in place. The old array silently changes
-- its representation without changing the visible behavior:
-- it stores a link to the new current array along with the
-- difference to be applied to get the old contents.
--
-- So if a diff array is used in a single-threaded style,
-- i.e. after '//' application the old version is no longer used,
-- @a'!'i@ takes O(1) time and @a '//' d@ takes O(@length d@).
-- Accessing elements of older versions gradually becomes slower.
--
-- Updating an array which is not current makes a physical copy.
-- The resulting array is unlinked from the old family. So you
-- can obtain a version which is guaranteed to be current and
-- thus have fast element access by @a '//' []@.
-- Possible improvement for the future (not implemented now):
-- make it possible to say "I will make an update now, but when
-- I later return to the old version, I want it to mutate back
-- instead of being copied".
IOToDiffArray, -- data IOToDiffArray
-- (a :: * -> * -> *) -- internal mutable array
-- (i :: *) -- indices
-- (e :: *) -- elements
-- | Type synonyms for the two most important IO array types.
-- Two most important diff array types are fully polymorphic
-- lazy boxed DiffArray:
DiffArray, -- = IOToDiffArray IOArray
-- ...and strict unboxed DiffUArray, working only for elements
-- of primitive types but more compact and usually faster:
DiffUArray, -- = IOToDiffArray IOUArray
-- * Overloaded immutable array interface
-- | Module "Data.Array.IArray" provides the interface of diff arrays.
-- They are instances of class 'IArray'.
module Data.Array.IArray,
-- * Low-level interface
-- | These are really internal functions, but you will need them
-- to make further 'IArray' instances of various diff array types
-- (for either more 'MArray' types or more unboxed element types).
newDiffArray, readDiffArray, replaceDiffArray
)
where
------------------------------------------------------------------------
-- Imports.
import Prelude
import Data.Ix
import Data.Array.Base
import Data.Array.IArray
import Data.Array.IO
import Foreign.Ptr ( Ptr, FunPtr )
import Foreign.StablePtr ( StablePtr )
import Data.Int ( Int8, Int16, Int32, Int64 )
import Data.Word ( Word, Word8, Word16, Word32, Word64 )
import System.IO.Unsafe ( unsafePerformIO )
import Control.Exception ( evaluate )
import Control.Concurrent.MVar ( MVar, newMVar, takeMVar, putMVar, readMVar )
------------------------------------------------------------------------
-- Diff array types.
-- | An arbitrary 'MArray' type living in the 'IO' monad can be converted
-- to a diff array.
newtype IOToDiffArray a i e =
DiffArray {varDiffArray :: MVar (DiffArrayData a i e)}
-- Internal representation: either a mutable array, or a link to
-- another diff array patched with a list of index+element pairs.
data DiffArrayData a i e = Current (a i e)
| Diff (IOToDiffArray a i e) [(Int, e)]
-- | Fully polymorphic lazy boxed diff array.
type DiffArray = IOToDiffArray IOArray
-- | Strict unboxed diff array, working only for elements
-- of primitive types but more compact and usually faster than 'DiffArray'.
type DiffUArray = IOToDiffArray IOUArray
-- Having 'MArray a e IO' in instance context would require
-- -fallow-undecidable-instances, so each instance is separate here.
------------------------------------------------------------------------
-- Showing DiffArrays
instance (Ix ix, Show ix, Show e) => Show (DiffArray ix e) where
showsPrec = showsIArray
instance (Ix ix, Show ix) => Show (DiffUArray ix Char) where
showsPrec = showsIArray
instance (Ix ix, Show ix) => Show (DiffUArray ix Int) where
showsPrec = showsIArray
instance (Ix ix, Show ix) => Show (DiffUArray ix Word) where
showsPrec = showsIArray
instance (Ix ix, Show ix) => Show (DiffUArray ix Float) where
showsPrec = showsIArray
instance (Ix ix, Show ix) => Show (DiffUArray ix Double) where
showsPrec = showsIArray
instance (Ix ix, Show ix) => Show (DiffUArray ix Int8) where
showsPrec = showsIArray
instance (Ix ix, Show ix) => Show (DiffUArray ix Int16) where
showsPrec = showsIArray
instance (Ix ix, Show ix) => Show (DiffUArray ix Int32) where
showsPrec = showsIArray
instance (Ix ix, Show ix) => Show (DiffUArray ix Int64) where
showsPrec = showsIArray
instance (Ix ix, Show ix) => Show (DiffUArray ix Word8) where
showsPrec = showsIArray
instance (Ix ix, Show ix) => Show (DiffUArray ix Word16) where
showsPrec = showsIArray
instance (Ix ix, Show ix) => Show (DiffUArray ix Word32) where
showsPrec = showsIArray
instance (Ix ix, Show ix) => Show (DiffUArray ix Word64) where
showsPrec = showsIArray
------------------------------------------------------------------------
-- Boring instances.
instance IArray (IOToDiffArray IOArray) e where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray1` ies
instance IArray (IOToDiffArray IOUArray) Char where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
instance IArray (IOToDiffArray IOUArray) Int where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
instance IArray (IOToDiffArray IOUArray) Word where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
instance IArray (IOToDiffArray IOUArray) (Ptr a) where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
instance IArray (IOToDiffArray IOUArray) (FunPtr a) where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
instance IArray (IOToDiffArray IOUArray) Float where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
instance IArray (IOToDiffArray IOUArray) Double where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
instance IArray (IOToDiffArray IOUArray) (StablePtr a) where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
instance IArray (IOToDiffArray IOUArray) Int8 where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
instance IArray (IOToDiffArray IOUArray) Int16 where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
instance IArray (IOToDiffArray IOUArray) Int32 where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
instance IArray (IOToDiffArray IOUArray) Int64 where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
instance IArray (IOToDiffArray IOUArray) Word8 where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
instance IArray (IOToDiffArray IOUArray) Word16 where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
instance IArray (IOToDiffArray IOUArray) Word32 where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
instance IArray (IOToDiffArray IOUArray) Word64 where
bounds a = unsafePerformIO $ boundsDiffArray a
unsafeArray lu ies = unsafePerformIO $ newDiffArray lu ies
unsafeAt a i = unsafePerformIO $ a `readDiffArray` i
unsafeReplace a ies = unsafePerformIO $ a `replaceDiffArray2` ies
------------------------------------------------------------------------
-- The important stuff.
newDiffArray :: (MArray a e IO, Ix i)
=> (i,i)
-> [(Int, e)]
-> IO (IOToDiffArray a i e)
newDiffArray (l,u) ies = do
a <- newArray_ (l,u)
sequence_ [unsafeWrite a i e | (i, e) <- ies]
var <- newMVar (Current a)
return (DiffArray var)
readDiffArray :: (MArray a e IO, Ix i)
=> IOToDiffArray a i e
-> Int
-> IO e
a `readDiffArray` i = do
d <- readMVar (varDiffArray a)
case d of
Current a' -> unsafeRead a' i
Diff a' ies -> maybe (readDiffArray a' i) return (lookup i ies)
replaceDiffArray :: (MArray a e IO, Ix i)
=> IOToDiffArray a i e
-> [(Int, e)]
-> IO (IOToDiffArray a i e)
a `replaceDiffArray` ies = do
d <- takeMVar (varDiffArray a)
case d of
Current a' -> case ies of
[] -> do
-- We don't do the copy when there is nothing to change
-- and this is the current version. But see below.
putMVar (varDiffArray a) d
return a
_:_ -> do
diff <- sequence [do e <- unsafeRead a' i; return (i, e)
| (i, _) <- ies]
sequence_ [unsafeWrite a' i e | (i, e) <- ies]
var' <- newMVar (Current a')
putMVar (varDiffArray a) (Diff (DiffArray var') diff)
return (DiffArray var')
Diff _ _ -> do
-- We still do the copy when there is nothing to change
-- but this is not the current version. So you can use
-- 'a // []' to make sure that the resulting array has
-- fast element access.
putMVar (varDiffArray a) d
a' <- thawDiffArray a
-- thawDiffArray gives a fresh array which we can
-- safely mutate.
sequence_ [unsafeWrite a' i e | (i, e) <- ies]
var' <- newMVar (Current a')
return (DiffArray var')
-- The elements of the diff list might recursively reference the
-- array, so we must seq them before taking the MVar to avoid
-- deadlock.
replaceDiffArray1 :: (MArray a e IO, Ix i)
=> IOToDiffArray a i e
-> [(Int, e)]
-> IO (IOToDiffArray a i e)
a `replaceDiffArray1` ies = do
mapM_ (evaluate . fst) ies
a `replaceDiffArray` ies
-- If the array contains unboxed elements, then the elements of the
-- diff list may also recursively reference the array from inside
-- replaceDiffArray, so we must seq them too.
replaceDiffArray2 :: (MArray a e IO, Ix i)
=> IOToDiffArray a i e
-> [(Int, e)]
-> IO (IOToDiffArray a i e)
a `replaceDiffArray2` ies = do
mapM_ (\(a,b) -> do evaluate a; evaluate b) ies
a `replaceDiffArray` ies
boundsDiffArray :: (MArray a e IO, Ix ix)
=> IOToDiffArray a ix e
-> IO (ix,ix)
boundsDiffArray a = do
d <- readMVar (varDiffArray a)
case d of
Current a' -> getBounds a'
Diff a' _ -> boundsDiffArray a'
freezeDiffArray :: (MArray a e IO, Ix ix)
=> a ix e
-> IO (IOToDiffArray a ix e)
freezeDiffArray a = do
(l,u) <- getBounds a
a' <- newArray_ (l,u)
sequence_ [unsafeRead a i >>= unsafeWrite a' i | i <- [0 .. rangeSize (l,u) - 1]]
var <- newMVar (Current a')
return (DiffArray var)
{-# RULES
"freeze/DiffArray" freeze = freezeDiffArray
#-}
-- unsafeFreezeDiffArray is really unsafe. Better don't use the old
-- array at all after freezing. The contents of the source array will
-- be changed when '//' is applied to the resulting array.
unsafeFreezeDiffArray :: (MArray a e IO, Ix ix)
=> a ix e
-> IO (IOToDiffArray a ix e)
unsafeFreezeDiffArray a = do
var <- newMVar (Current a)
return (DiffArray var)
{-# RULES
"unsafeFreeze/DiffArray" unsafeFreeze = unsafeFreezeDiffArray
#-}
thawDiffArray :: (MArray a e IO, Ix ix)
=> IOToDiffArray a ix e
-> IO (a ix e)
thawDiffArray a = do
d <- readMVar (varDiffArray a)
case d of
Current a' -> do
(l,u) <- getBounds a'
a'' <- newArray_ (l,u)
sequence_ [unsafeRead a' i >>= unsafeWrite a'' i | i <- [0 .. rangeSize (l,u) - 1]]
return a''
Diff a' ies -> do
a'' <- thawDiffArray a'
sequence_ [unsafeWrite a'' i e | (i, e) <- ies]
return a''
{-# RULES
"thaw/DiffArray" thaw = thawDiffArray
#-}
-- unsafeThawDiffArray is really unsafe. Better don't use the old
-- array at all after thawing. The contents of the resulting array
-- will be changed when '//' is applied to the source array.
unsafeThawDiffArray :: (MArray a e IO, Ix ix)
=> IOToDiffArray a ix e
-> IO (a ix e)
unsafeThawDiffArray a = do
d <- readMVar (varDiffArray a)
case d of
Current a' -> return a'
Diff a' ies -> do
a'' <- unsafeThawDiffArray a'
sequence_ [unsafeWrite a'' i e | (i, e) <- ies]
return a''
{-# RULES
"unsafeThaw/DiffArray" unsafeThaw = unsafeThawDiffArray
#-}
|