PrimitiveArray-0.10.0.0: lib/Data/PrimitiveArray/Class.hs
-- | Vastly extended primitive arrays. Some basic ideas are now modeled after
-- the vector package, especially the monadic mutable / pure immutable array
-- system.
--
-- NOTE all operations in MPrimArrayOps and PrimArrayOps are highly unsafe. No
-- bounds-checking is performed at all.
module Data.PrimitiveArray.Class where
import Control.Applicative (Applicative, pure, (<$>), (<*>))
import Control.Exception (assert)
import Control.Monad.Except
import Control.Monad (forM_)
import Control.Monad.Primitive (PrimMonad, liftPrim)
import Control.Monad.ST (runST)
import Data.Proxy
import Data.Vector.Fusion.Util
import Debug.Trace
import GHC.Generics (Generic)
import Prelude as P
import qualified Data.Vector.Fusion.Stream.Monadic as SM
import Data.PrimitiveArray.Index.Class
-- | Mutable version of an array.
data family MutArr (m :: * -> *) (arr :: *) :: *
-- | The core set of operations for monadic arrays.
class (Index sh) => MPrimArrayOps arr sh elm where
-- | Return the bounds of the array. All bounds are inclusive, as in
-- @[lb..ub]@
upperBoundM :: MutArr m (arr sh elm) -> LimitType sh
-- | Given lower and upper bounds and a list of /all/ elements, produce a
-- mutable array.
fromListM :: PrimMonad m => LimitType sh -> [elm] -> m (MutArr m (arr sh elm))
-- | Creates a new array with the given bounds with each element within the
-- array being in an undefined state.
newM :: PrimMonad m => LimitType sh -> m (MutArr m (arr sh elm))
-- | Creates a new array with all elements being equal to 'elm'.
newWithM :: PrimMonad m => LimitType sh -> elm -> m (MutArr m (arr sh elm))
-- | Reads a single element in the array.
readM :: PrimMonad m => MutArr m (arr sh elm) -> sh -> m elm
-- | Writes a single element in the array.
writeM :: PrimMonad m => MutArr m (arr sh elm) -> sh -> elm -> m ()
-- | The core set of functions on immutable arrays.
class (Index sh) => PrimArrayOps arr sh elm where
-- | Returns the bounds of an immutable array, again inclusive bounds: @ [lb..ub] @.
upperBound :: arr sh elm -> LimitType sh
-- | Freezes a mutable array an returns its immutable version. This operation
-- is /O(1)/ and both arrays share the same memory. Do not use the mutable
-- array afterwards.
unsafeFreeze :: PrimMonad m => MutArr m (arr sh elm) -> m (arr sh elm)
-- | Thaw an immutable array into a mutable one. Both versions share
-- memory.
unsafeThaw :: PrimMonad m => arr sh elm -> m (MutArr m (arr sh elm))
-- | Extract a single element from the array. Generally unsafe as not
-- bounds-checking is performed.
unsafeIndex :: arr sh elm -> sh -> elm
-- | Savely transform the shape space of a table.
transformShape :: (Index sh') => (LimitType sh -> LimitType sh') -> arr sh elm -> arr sh' elm
class (Index sh) => PrimArrayMap arr sh e e' where
-- | Map a function over each element, keeping the shape intact.
map :: (e -> e') -> arr sh e -> arr sh e'
data PAErrors
= PAEUpperBound
deriving (Eq,Generic)
instance Show PAErrors where
show (PAEUpperBound) = "Upper bound is too large for @Int@ size!"
-- | Infix index operator. Performs minimal bounds-checking using assert in
-- non-optimized code.
(!) :: PrimArrayOps arr sh elm => arr sh elm -> sh -> elm
(!) arr idx = assert (inBounds (upperBound arr) idx) $ unsafeIndex arr idx
{-# INLINE (!) #-}
-- | Returns true if the index is valid for the array.
inBoundsM :: (Monad m, MPrimArrayOps arr sh elm) => MutArr m (arr sh elm) -> sh -> Bool
inBoundsM marr idx = inBounds (upperBoundM marr) idx
{-# INLINE inBoundsM #-}
-- -- | Given two arrays with the same dimensionality, their respective starting
-- -- index, and how many steps to go in each dimension (in terms of a dimension
-- -- again), determine if the multidimensional slices have the same value at
-- -- all positions
-- --
-- -- TODO specialize for DIM1 (and maybe higher dim's) to use memcmp
--
-- sliceEq :: (Eq elm, PrimArrayOps arr sh elm) => arr sh elm -> sh -> arr sh elm -> sh -> sh -> Bool
-- sliceEq arr1 k1 arr2 k2 xtnd = assert ((inBounds arr1 k1) && (inBounds arr2 k2) && (inBounds arr1 $ k1 `addDim` xtnd) && (inBounds arr2 $ k2 `addDim` xtnd)) $ and res where
-- res = zipWith (==) xs ys
-- xs = P.map (unsafeIndex arr1) $ rangeList k1 xtnd
-- ys = P.map (unsafeIndex arr2) $ rangeList k2 xtnd
-- {-# INLINE sliceEq #-}
-- | Construct a mutable primitive array from a lower and an upper bound, a
-- default element, and a list of associations.
fromAssocsM
:: (PrimMonad m, MPrimArrayOps arr sh elm)
=> LimitType sh -> elm -> [(sh,elm)] -> m (MutArr m (arr sh elm))
fromAssocsM ub def xs = do
ma <- newWithM ub def
-- let s = size ub
-- traceShow (s,length xs) $ when (s < length xs) $ error "bang"
forM_ xs $ \(k,v) -> writeM ma k v
return ma
{-# INLINE fromAssocsM #-}
-- | Initialize an immutable array but stay within the primitive monad @m@.
newWithPA
∷ (PrimMonad m, MPrimArrayOps arr sh elm, PrimArrayOps arr sh elm)
⇒ LimitType sh
→ elm
→ m (arr sh elm)
newWithPA ub def = do
ma ← newWithM ub def
unsafeFreeze ma
{-# Inlinable newWithPA #-}
-- | Safely prepare a primitive array.
--
-- TODO Check if having a 'MonadError' instance degrades performance. (We
-- should see this once the test with NeedlemanWunsch is under way).
safeNewWithPA
∷ forall m arr sh elm
. (PrimMonad m, MonadError PAErrors m, MPrimArrayOps arr sh elm, PrimArrayOps arr sh elm)
⇒ LimitType sh
→ elm
→ m (arr sh elm)
safeNewWithPA ub def = do
case runExcept $ sizeIsValid maxBound [totalSize ub] of
Left (SizeError _) → throwError PAEUpperBound
Right (CellSize _) → newWithPA ub def
{-# Inlinable safeNewWithPA #-}
-- | Return all associations from an array.
assocs :: forall arr sh elm . (IndexStream sh, PrimArrayOps arr sh elm) => arr sh elm -> [(sh,elm)]
assocs arr = unId . SM.toList $ assocsS arr
{-# INLINE assocs #-}
-- | Return all associations from an array.
assocsS ∷ forall m arr sh elm . (Monad m, IndexStream sh, PrimArrayOps arr sh elm) ⇒ arr sh elm → SM.Stream m (sh,elm)
assocsS arr = SM.map (\k -> (k,unsafeIndex arr k)) $ streamUp zeroBound' (upperBound arr)
{-# INLINE assocsS #-}
-- | Creates an immutable array from lower and upper bounds and a complete list
-- of elements.
fromList :: (PrimArrayOps arr sh elm, MPrimArrayOps arr sh elm) => LimitType sh -> [elm] -> arr sh elm
fromList ub xs = runST $ fromListM ub xs >>= unsafeFreeze
{-# INLINE fromList #-}
-- | Creates an immutable array from lower and upper bounds, a default element,
-- and a list of associations.
fromAssocs :: (PrimArrayOps arr sh elm, MPrimArrayOps arr sh elm) => LimitType sh -> elm -> [(sh,elm)] -> arr sh elm
fromAssocs ub def xs = runST $ fromAssocsM ub def xs >>= unsafeFreeze
{-# INLINE fromAssocs #-}
-- -- | Determines if an index is valid for a given immutable array.
--
-- inBounds :: PrimArrayOps arr sh elm => arr sh elm -> sh -> Bool
-- inBounds arr idx = let (lb,ub) = bounds arr in inShapeRange lb (ub `addDim` unitDim) idx
-- {-# INLINE inBounds #-}
-- | Returns all elements of an immutable array as a list.
toList :: forall arr sh elm . (IndexStream sh, PrimArrayOps arr sh elm) => arr sh elm -> [elm]
toList arr = let ub = upperBound arr in P.map ((!) arr) . unId . SM.toList $ streamUp zeroBound' ub
{-# INLINE toList #-}
-- * Freeze an inductive stack of tables with a 'Z' at the bottom.
-- | 'freezeTables' freezes a stack of tables.
class FreezeTables m t where
type Frozen t :: *
freezeTables :: t -> m (Frozen t)
instance Applicative m => FreezeTables m Z where
type Frozen Z = Z
freezeTables Z = pure Z
{-# INLINE freezeTables #-}
instance (Functor m, Applicative m, Monad m, PrimMonad m, FreezeTables m ts, PrimArrayOps arr sh elm) => FreezeTables m (ts:.MutArr m (arr sh elm)) where
type Frozen (ts:.MutArr m (arr sh elm)) = Frozen ts :. arr sh elm
freezeTables (ts:.t) = (:.) <$> freezeTables ts <*> unsafeFreeze t
{-# INLINE freezeTables #-}