primitive-maybe-0.1.1: src/Data/Primitive/Array/Maybe.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
-- | This provides an interface to working with boxed arrays
-- with elements of type @Maybe a@. That is:
--
-- > MaybeArray a ≅ Array (Maybe a)
--
-- However, this type provided by this module is more efficient
-- than its naive @Array@ counterpart. It consumes less
-- memory and has fewer heap indirections.
module Data.Primitive.Array.Maybe
( MaybeArray
, MutableMaybeArray
, indexMaybeArray
, newMaybeArray
, readMaybeArray
, writeMaybeArray
, sequenceMaybeArray
, unsafeFreezeMaybeArray
, thawMaybeArray
, maybeArrayFromList
, maybeArrayFromListN
, sizeofMaybeArray
) where
import Prelude hiding (zipWith)
import Control.Applicative (Alternative(..), liftA2)
import Control.Monad (when, MonadPlus(..))
import Control.Monad.Fail (MonadFail(..))
import Control.Monad.ST (ST, runST)
import Control.Monad.Zip (MonadZip(..))
import Control.Monad.Primitive
import Data.Primitive.Array
import Data.Primitive.UnliftedArray (PrimUnlifted)
import Data.Foldable hiding (toList)
import Data.Functor.Classes
import qualified Data.Foldable as Foldable
import Data.Maybe (maybe)
import Data.Data
(Data(..), DataType, mkDataType, Constr, mkConstr, Fixity(..), constrIndex)
import Data.Primitive.Maybe.Internal
import GHC.Exts (Any,reallyUnsafePtrEquality#, Int(..), IsList(..), MutableArray#)
import Text.ParserCombinators.ReadP
import Unsafe.Coerce (unsafeCoerce)
-- | An immutable array of boxed values of type @'Maybe' a@.
newtype MaybeArray a = MaybeArray (Array Any)
deriving (PrimUnlifted)
-- | A mutable array of boxed values of type @'Maybe' a@.
newtype MutableMaybeArray s a = MutableMaybeArray (MutableArray s Any)
deriving (PrimUnlifted)
type role MaybeArray representational
type role MutableMaybeArray nominal representational
instance Functor MaybeArray where
fmap :: forall a b. (a -> b) -> MaybeArray a -> MaybeArray b
fmap f (MaybeArray arr) = MaybeArray $
createArray (sizeofArray arr) (error "impossible") $ \mb ->
let go i
| i == (sizeofArray arr) = return ()
| otherwise = do
x <- indexArrayM arr i
case unsafeToMaybe x of
Nothing -> pure ()
Just val -> writeArray mb i (toAny (f val))
go (i + 1)
in go 0
e <$ (MaybeArray a) = MaybeArray $ createArray (sizeofArray a) (toAny e) (\ !_ -> pure ())
instance Applicative MaybeArray where
pure :: a -> MaybeArray a
pure a = MaybeArray $ runArray $ newArray 1 (toAny a)
(<*>) :: MaybeArray (a -> b) -> MaybeArray a -> MaybeArray b
abm@(MaybeArray ab) <*> am@(MaybeArray a) = MaybeArray $ createArray (szab * sza) nothingSurrogate $ \mb ->
let go1 i = when (i < szab) $ do
case indexMaybeArray abm i of
Nothing -> pure ()
Just f -> go2 (i * sza) f 0
go1 (i + 1)
go2 off f j = when (j < sza) $ do
case indexMaybeArray am j of
Nothing -> pure ()
Just v -> writeArray mb (off + j) (toAny (f v))
go2 off f (j + 1)
in go1 0
where szab = sizeofArray ab; sza = sizeofArray a
MaybeArray a *> MaybeArray b = MaybeArray $ createArray (sza * szb) nothingSurrogate $ \mb ->
let go i | i < sza = copyArray mb (i * szb) b 0 szb
| otherwise = return ()
in go 0
where sza = sizeofArray a; szb = sizeofArray b
MaybeArray a <* MaybeArray b = MaybeArray $ createArray (sza*szb) nothingSurrogate $ \ma ->
let fill off i e | i < szb = writeArray ma (off+i) e >> fill off (i+1) e
| otherwise = return ()
go i | i < sza
= do x <- indexArrayM a i
fill (i*szb) 0 x >> go (i+1)
| otherwise = return ()
in go 0
where sza = sizeofArray a ; szb = sizeofArray b
instance Traversable MaybeArray where
traverse = traverseArray
traverseArray :: Applicative f
=> (a -> f b)
-> MaybeArray a
-> f (MaybeArray b)
traverseArray f = \ !(MaybeArray ary) ->
let
!len = sizeofArray ary
go !ix
| ix == len = pure $ STA $ \mary -> unsafeFreezeArray (MutableArray mary)
| otherwise = let x = indexArray ary ix
in case unsafeToMaybe x of
Nothing -> go (ix + 1)
Just v -> liftA2 (\b (STA m) -> STA $ \mary ->
writeArray (MutableArray mary) ix (toAny b) >> m mary)
(f v) (go (ix + 1))
in if len == 0
then pure mempty
else MaybeArray <$> runSTA len <$> go 0
newtype STA a = STA { _runSTA :: forall s. MutableArray# s a -> ST s (Array a) }
runSTA :: Int -> STA a -> Array a
runSTA !sz = \(STA m) -> runST $ newArray_ sz >>= \ar -> m (marray# ar)
newArray_ :: Int -> ST s (MutableArray s a)
newArray_ !n = newArray n badTraverseValue
badTraverseValue :: a
badTraverseValue = error "traverse: bad indexing"
instance Alternative MaybeArray where
empty = mempty
(<|>) = (<>)
some a | sizeofMaybeArray a == 0 = mempty
| otherwise = error "some: infinite arrays are not well defined"
many a | sizeofMaybeArray a == 0 = pure []
| otherwise = error "many: infinite arrays are not well defined"
instance MonadPlus MaybeArray where
mzero = empty
mplus = (<|>)
instance MonadFail MaybeArray where
fail _ = empty
zipWith :: (a -> b -> c) -> MaybeArray a -> MaybeArray b -> MaybeArray c
zipWith f (MaybeArray aa) (MaybeArray ab) = MaybeArray $
createArray mn nothingSurrogate $ \mc ->
let go i
| i < mn = do
x <- indexArrayM aa i
y <- indexArrayM ab i
let x' = unsafeToMaybe x
y' = unsafeToMaybe y
case x' of
Nothing -> go (i + 1)
Just va -> case y' of
Nothing -> go (i + 1)
Just vb -> writeArray mc i (toAny $ f va vb) >> go (i + 1)
| otherwise = return ()
in go 0
where mn = sizeofArray aa `min` sizeofArray ab
instance MonadZip MaybeArray where
mzip aa ab = zipWith (,) aa ab
mzipWith f aa ab = zipWith f aa ab
munzip :: forall a b. MaybeArray (a, b) -> (MaybeArray a, MaybeArray b)
munzip (MaybeArray aab) = runST $ do
let sz = sizeofArray aab
ma_ <- newArray sz nothingSurrogate :: ST s (MutableArray s Any)
mb_ <- newArray sz nothingSurrogate :: ST s (MutableArray s Any)
let go :: forall s. Int -> MutableArray s Any -> MutableArray s Any -> ST s ()
go i ma mb = if i < sz
then do
tab <- indexArrayM aab i
let (a, b) = fromAny tab
a' = unsafeToMaybe a
b' = unsafeToMaybe b
maybe (pure ()) (writeArray ma i) a'
maybe (pure ()) (writeArray mb i) b'
go (i + 1) ma mb
else return ()
go 0 ma_ mb_
(ma1, ma2) <- (,) <$> unsafeFreezeArray ma_ <*> unsafeFreezeArray mb_
return (unsafeCoerce ma1, unsafeCoerce ma2) :: ST s (MaybeArray a, MaybeArray b)
data ArrayStack a
= PushArray !(Array a) !(ArrayStack a)
| EmptyStack
instance Monad MaybeArray where
return = pure
(>>) = (*>)
(MaybeArray ary) >>= f = MaybeArray $ collect 0 EmptyStack (la - 1)
where
la = sizeofArray ary
collect sz stk i
| i < 0 = createArray sz nothingSurrogate $ fill 0 stk
| otherwise = let x = indexArray ary i
in case unsafeToMaybe x of
Nothing -> collect sz stk (i - 1)
Just v -> let (MaybeArray sb) = f v
lsb = sizeofArray sb
in if lsb == 0
then collect sz stk (i - 1)
else collect (sz + lsb) (PushArray sb stk) (i - 1)
fill _ EmptyStack _ = return ()
fill off (PushArray sb sbs) smb
| let lsb = sizeofArray sb
= copyArray smb off sb 0 lsb
*> fill (off + lsb) sbs smb
instance Foldable MaybeArray where
-- Note: we perform the array lookups eagerly so we won't
-- create thunks to perform lookups even if GHC can't see
-- that the folding function is strict.
foldr f = \z !(MaybeArray ary) ->
let
!sz = sizeofArray ary
go i
| i == sz = z
| otherwise = let !x = indexArray ary i
in case unsafeToMaybe x of
Nothing -> z
Just val -> f val (go (i + 1))
in go 0
{-# INLINE foldr #-}
foldl f = \z !(MaybeArray ary) ->
let
go i
| i < 0 = z
| otherwise = let !x = indexArray ary i
in case unsafeToMaybe x of
Nothing -> z
Just val -> f (go (i - 1)) val
in go (sizeofArray ary - 1)
{-# INLINE foldl #-}
null (MaybeArray a) = sizeofArray a == 0
{-# INLINE null #-}
length (MaybeArray a) = sizeofArray a
{-# INLINE length #-}
sum = foldl' (+) 0
{-# INLINE sum #-}
product = foldl' (*) 1
{-# INLINE product #-}
instance Semigroup (MaybeArray a) where
(<>) :: MaybeArray a -> MaybeArray a -> MaybeArray a
MaybeArray a1 <> MaybeArray a2 = MaybeArray $
createArray (sza1 + sza2) nothingSurrogate $ \ma ->
copyArray ma 0 a1 0 sza1 >> copyArray ma sza1 a2 0 sza2
where
sza1 = sizeofArray a1; sza2 = sizeofArray a2
instance Monoid (MaybeArray a) where
mempty = MaybeArray emptyArray
mappend = (<>)
instance IsList (MaybeArray a) where
type Item (MaybeArray a) = a
fromListN = maybeArrayFromListN
fromList = maybeArrayFromList
toList = Foldable.toList
instance Eq a => Eq (MaybeArray a) where
sma1 == sma2 = maybeArrayLiftEq (==) sma1 sma2
instance Eq1 MaybeArray where
liftEq = maybeArrayLiftEq
instance Ord1 MaybeArray where
liftCompare = maybeArrayLiftCompare
maybeArrayLiftEq :: (a -> b -> Bool) -> MaybeArray a -> MaybeArray b -> Bool
maybeArrayLiftEq p (MaybeArray sa1) (MaybeArray sa2) = length sa1 == length sa2 && loop (length sa1 - 1)
where
loop i
| i < 0 = True
| otherwise = let x = unsafeToMaybe (indexArray sa1 i)
y = unsafeToMaybe (indexArray sa2 i)
in case x of
Nothing -> case y of
Nothing -> True && loop (i - 1)
_ -> False
Just x' -> case y of
Nothing -> False
Just y' -> p x' y' && loop (i - 1)
maybeArrayLiftCompare :: (a -> b -> Ordering) -> MaybeArray a -> MaybeArray b -> Ordering
maybeArrayLiftCompare elemCompare (MaybeArray a1) (MaybeArray a2) = loop 0
where
la1 = length a1
la2 = length a2
mn = la1 `min` la2
loop i
| i < mn = let x = unsafeToMaybe (indexArray a1 i)
y = unsafeToMaybe (indexArray a2 i)
in case x of
Nothing -> case y of
Nothing -> EQ `mappend` loop (i + 1)
_ -> LT
Just x' -> case y of
Nothing -> GT
Just y' -> elemCompare x' y' `mappend` loop (i + 1)
| otherwise = compare la1 la2
instance Ord a => Ord (MaybeArray a) where
compare sma1 sma2 = maybeArrayLiftCompare compare sma1 sma2
maybeArrayLiftShowsPrec :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> Int -> MaybeArray a -> ShowS
maybeArrayLiftShowsPrec elemShowsPrec elemListShowsPrec p sa = showParen (p > 10) $
showString "fromListN " . shows (length sa) . showString " "
. listLiftShowsPrec elemShowsPrec elemListShowsPrec 11 (toList sa)
listLiftShowsPrec :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> Int -> [a] -> ShowS
listLiftShowsPrec _ sl _ = sl
instance Show1 MaybeArray where
liftShowsPrec = maybeArrayLiftShowsPrec
instance Show a => Show (MaybeArray a) where
showsPrec p sa = maybeArrayLiftShowsPrec showsPrec showList p sa
maybeArrayLiftReadsPrec :: (Int -> ReadS a) -> ReadS [a] -> Int -> ReadS (MaybeArray a)
maybeArrayLiftReadsPrec _ listReadsPrec p = readParen (p > 10) . readP_to_S $ do
() <$ string "fromListN"
skipSpaces
n <- readS_to_P reads
skipSpaces
l <- readS_to_P listReadsPrec
return $ maybeArrayFromListN n l
instance Read1 MaybeArray where
liftReadsPrec = maybeArrayLiftReadsPrec
instance Read a => Read (MaybeArray a) where
readsPrec = maybeArrayLiftReadsPrec readsPrec readList
maybeArrayDataType :: DataType
maybeArrayDataType = mkDataType "Data.Primitive.Array.Maybe.MaybeArray" [fromListConstr]
fromListConstr :: Constr
fromListConstr = mkConstr maybeArrayDataType "fromList" [] Prefix
instance Data a => Data (MaybeArray a) where
toConstr _ = fromListConstr
dataTypeOf _ = maybeArrayDataType
gunfold k z c = case constrIndex c of
1 -> k (z fromList)
_ -> error "gunfold"
gfoldl f z m = z fromList `f` toList m
-- | Create a new 'MutableMaybeArray' of the given size and initialize all elements
-- with the given 'Maybe' value.
newMaybeArray :: PrimMonad m => Int -> Maybe a -> m (MutableMaybeArray (PrimState m) a)
{-# INLINE newMaybeArray #-}
newMaybeArray i ma = case ma of
Just a -> do
x <- newArray i (unsafeCoerce a)
return (MutableMaybeArray x)
Nothing -> do
x <- newArray i nothingSurrogate
return (MutableMaybeArray x)
-- | Get the 'Maybe' value at the given index out of a 'MaybeArray'.
indexMaybeArray :: MaybeArray a -> Int -> Maybe a
{-# INLINE indexMaybeArray #-}
indexMaybeArray (MaybeArray a) ix =
let (# v #) = indexArray## a ix
in unsafeToMaybe v
-- | Get the 'Maybe' value at the given index out of a 'MutableMaybeArray'.
readMaybeArray :: PrimMonad m => MutableMaybeArray (PrimState m) a -> Int -> m (Maybe a)
{-# INLINE readMaybeArray #-}
readMaybeArray (MutableMaybeArray m) ix = do
a <- readArray m ix
return (unsafeToMaybe a)
-- | Write a 'Maybe' value to the given index of a 'MutableMaybeArray'.
writeMaybeArray :: PrimMonad m => MutableMaybeArray (PrimState m) a -> Int -> Maybe a -> m ()
{-# INLINE writeMaybeArray #-}
writeMaybeArray (MutableMaybeArray marr) ix ma = case ma of
Just a -> writeArray marr ix (unsafeCoerce a)
Nothing -> writeArray marr ix nothingSurrogate
-- | This is like calling @sequence@ on an 'Array'. However, in
-- the event that all the values are @Just@, it does not need
-- to allocate a new array since the array backing the @MaybeArray@
-- can be reused.
sequenceMaybeArray :: MaybeArray a -> Maybe (Array a)
sequenceMaybeArray m@(MaybeArray a) =
if hasNothing m then Nothing else Just (unsafeCoerce a)
-- | Returns @True@ if the 'MaybeArray' contains a @Nothing@ value.
hasNothing :: MaybeArray a -> Bool
hasNothing (MaybeArray a) = go 0 where
go !ix = if ix < sizeofArray a
then
let (# v #) = indexArray## a ix
in case reallyUnsafePtrEquality# v nothingSurrogate of
0# -> go (ix + 1)
_ -> True
else False
-- | Convert a 'MutableMaybeArray' to an immutable one without copying.
-- The array should not be modified after the conversion.
unsafeFreezeMaybeArray :: PrimMonad m => MutableMaybeArray (PrimState m) a -> m (MaybeArray a)
{-# INLINE unsafeFreezeMaybeArray #-}
unsafeFreezeMaybeArray (MutableMaybeArray ma) = do
a <- unsafeFreezeArray ma
return (MaybeArray a)
-- | Create a 'MutablePrimArray' from a slice of an immutable array.
-- This operation makes a copy of the specified slice, so it is safe
-- to use the immutable array afterward.
thawMaybeArray
:: PrimMonad m
=> MaybeArray a -- ^ source
-> Int -- ^ offset
-> Int -- ^ length
-> m (MutableMaybeArray (PrimState m) a)
thawMaybeArray (MaybeArray a) off len =
fmap MutableMaybeArray (thawArray a off len)
-- | Given the length of a list and a list of @a@,
-- build a 'MaybeArray' from the values in the list.
-- If the given 'Int' does not match the length of
-- the list, this function calls 'error'.
-- You should prefer this to 'maybeArrayFromList' if
-- the length of the list has already been computed.
maybeArrayFromListN :: Int -> [a] -> MaybeArray a
maybeArrayFromListN n l = MaybeArray $
createArray n (error "uninitialized element") $ \sma ->
let go !ix [] = if ix == n
then return ()
else error "list length less than specified size"
go !ix (x : xs) = if ix < n
then do
writeArray sma ix (toAny x)
go (ix+1) xs
else error "list length greater than specified size"
in go 0 l
-- | Given a list of @a@, build a 'MaybeArray' from
-- the values in the list.
maybeArrayFromList :: [a] -> MaybeArray a
maybeArrayFromList l = maybeArrayFromListN (length l) l
-- | Yield the size of the 'MaybeArray'.
sizeofMaybeArray :: MaybeArray a -> Int
sizeofMaybeArray (MaybeArray a) = sizeofArray a
{-# INLINE sizeofMaybeArray #-}