massiv-1.0.2.0: src/Data/Massiv/Core/List.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- |
-- Module : Data.Massiv.Core.List
-- Copyright : (c) Alexey Kuleshevich 2018-2022
-- License : BSD3
-- Maintainer : Alexey Kuleshevich <lehins@yandex.ru>
-- Stability : experimental
-- Portability : non-portable
--
module Data.Massiv.Core.List
( L(..)
, Array(..)
, List(..)
, toListArray
, showsArrayPrec
, showArrayList
, ListItem
) where
import Control.Monad (unless, when)
import Control.Scheduler
import Data.Coerce
import Data.Functor.Identity
import Data.Kind
import qualified Data.List as L
import Data.Massiv.Core.Common
import qualified Data.Massiv.Vector.Stream as S
import Data.Monoid
import Data.Typeable
import GHC.Exts
import GHC.TypeLits
import System.IO.Unsafe (unsafePerformIO)
type family ListItem ix e :: Type where
ListItem Ix1 e = e
ListItem ix e = [ListItem (Lower ix) e]
type family Elt ix e :: Type where
Elt Ix1 e = e
Elt ix e = List (Lower ix) e
newtype List ix e = List { unList :: [Elt ix e] }
instance Coercible (Elt ix e) (ListItem ix e) => IsList (List ix e) where
type Item (List ix e) = ListItem ix e
fromList = coerce
{-# INLINE fromList #-}
toList = coerce
{-# INLINE toList #-}
data L = L
data instance Array L ix e = LArray { lComp :: Comp
, lData :: !(List ix e)
}
instance Coercible (Elt ix e) (ListItem ix e) => IsList (Array L ix e) where
type Item (Array L ix e) = ListItem ix e
fromList = LArray Seq . coerce
{-# INLINE fromList #-}
toList = coerce . lData
{-# INLINE toList #-}
lengthHintList :: [a] -> LengthHint
lengthHintList =
\case
[] -> LengthExact zeroSz
_ -> LengthUnknown
{-# INLINE lengthHintList #-}
instance Shape L Ix1 where
linearSize = outerLength
{-# INLINE linearSize #-}
linearSizeHint = lengthHintList . unList . lData
{-# INLINE linearSizeHint #-}
isNull = null . unList . lData
{-# INLINE isNull #-}
outerSize = linearSize
{-# INLINE outerSize #-}
instance Shape L Ix2 where
linearSize = SafeSz . getSum . foldMap (Sum . length . unList) . unList . lData
{-# INLINE linearSize #-}
linearSizeHint = lengthHintList . unList . lData
{-# INLINE linearSizeHint #-}
isNull = getAll . foldMap (All . null . unList) . unList . lData
{-# INLINE isNull #-}
outerSize arr =
case unList (lData arr) of
[] -> zeroSz
(x:xs) -> SafeSz ((1 + length xs) :. length (unList x))
{-# INLINE outerSize #-}
instance (Shape L (Ix (n - 1)), Index (IxN n)) => Shape L (IxN n) where
linearSize = SafeSz . getSum . foldMap (Sum . unSz . linearSize . LArray Seq) . unList . lData
{-# INLINE linearSize #-}
linearSizeHint = lengthHintList . unList . lData
{-# INLINE linearSizeHint #-}
isNull = getAll . foldMap (All . isNull . LArray Seq) . unList . lData
{-# INLINE isNull #-}
outerSize arr =
case unList (lData arr) of
[] -> zeroSz
(x:xs) -> SafeSz ((1 + length xs) :> unSz (outerSize (LArray Seq x)))
{-# INLINE outerSize #-}
outerLength :: Array L ix e -> Sz Int
outerLength = SafeSz . length . unList . lData
{-# INLINE outerLength #-}
instance Ragged L Ix1 e where
flattenRagged = id
{-# INLINE flattenRagged #-}
generateRaggedM !comp !k f = do
xs <-
loopDeepM 0 (< coerce k) (+ 1) [] $ \i acc -> do
e <- f i
return (e : acc)
return $ LArray comp $ coerce xs
{-# INLINE generateRaggedM #-}
loadRaggedST _scheduler xs uWrite start end sz = go (unList (lData xs)) start
where
go (y:ys) i
| i < end = uWrite i y >> go ys (i + 1)
| otherwise = throwM $ DimTooLongException 1 sz (outerLength xs)
go [] i = when (i /= end) $ throwM $ DimTooShortException 1 sz (outerLength xs)
{-# INLINE loadRaggedST #-}
raggedFormat f _ arr = L.concat $ "[ " : L.intersperse ", " (map f (coerce (lData arr))) ++ [" ]"]
instance (Shape L ix, Ragged L ix e) => Load L ix e where
makeArray comp sz f = runIdentity $ generateRaggedM comp sz (pure . f)
{-# INLINE makeArray #-}
iterArrayLinearST_ scheduler arr uWrite =
loadRaggedST scheduler arr uWrite 0 (totalElem sz) sz
where !sz = outerSize arr
{-# INLINE iterArrayLinearST_ #-}
instance Ragged L Ix2 e where
generateRaggedM = unsafeGenerateParM
{-# INLINE generateRaggedM #-}
flattenRagged arr = LArray {lComp = lComp arr, lData = coerce xs}
where
xs = concatMap (unList . lData . flattenRagged . LArray (lComp arr)) (unList (lData arr))
{-# INLINE flattenRagged #-}
loadRaggedST scheduler xs uWrite start end sz
| isZeroSz sz = when (isNotNull (flattenRagged xs)) (throwM ShapeNonEmpty)
| otherwise = do
let (k, szL) = unconsSz sz
step = totalElem szL
leftOver <-
loopM start (< end) (+ step) (coerce (lData xs)) $ \i zs ->
case zs of
[] -> throwM (DimTooShortException 2 k (outerLength xs))
(y:ys) -> do
scheduleWork_ scheduler $
let end' = i + step
go (a:as) j
| j < end' = uWrite j a >> go as (j + 1)
| otherwise = throwM $ DimTooLongException 1 szL (Sz (length y))
go [] j = when (j /= end') $ throwM (DimTooShortException 1 szL (Sz (length y)))
in go y i
pure ys
unless (null leftOver) $ throwM $ DimTooLongException 2 k (outerLength xs)
{-# INLINE loadRaggedST #-}
raggedFormat f sep (LArray comp xs) =
showN (\s y -> raggedFormat f s (LArray comp y :: Array L Ix1 e)) sep (coerce xs)
instance ( Shape L (IxN n)
, Ragged L (Ix (n - 1)) e
, Coercible (Elt (Ix (n - 1)) e) (ListItem (Ix (n - 1)) e)
) =>
Ragged L (IxN n) e where
generateRaggedM = unsafeGenerateParM
{-# INLINE generateRaggedM #-}
flattenRagged arr = LArray {lComp = lComp arr, lData = coerce xs}
where
xs = concatMap (unList . lData . flattenRagged . LArray (lComp arr)) (unList (lData arr))
{-# INLINE flattenRagged #-}
loadRaggedST scheduler xs uWrite start end sz
| isZeroSz sz = when (isNotNull (flattenRagged xs)) (throwM ShapeNonEmpty)
| otherwise = do
let (k, szL) = unconsSz sz
step = totalElem szL
subScheduler
| end - start < numWorkers scheduler * step = scheduler
| otherwise = trivialScheduler_
leftOver <-
loopM start (< end) (+ step) (unList (lData xs)) $ \i zs ->
case zs of
[] -> throwM (DimTooShortException (dimensions sz) k (outerLength xs))
(y:ys) -> do
scheduleWork_ scheduler $
loadRaggedST subScheduler (LArray Seq y) uWrite i (i + step) szL
pure ys
unless (null leftOver) $ throwM $ DimTooLongException (dimensions sz) k (outerLength xs)
{-# INLINE loadRaggedST #-}
raggedFormat f sep (LArray comp xs) =
showN (\s y -> raggedFormat f s (LArray comp y :: Array L (Ix (n - 1)) e)) sep (coerce xs)
unsafeGenerateParM ::
(Elt ix e ~ List (Lower ix) e, Index ix, Monad m, Ragged L (Lower ix) e)
=> Comp
-> Sz ix
-> (ix -> m e)
-> m (Array L ix e)
unsafeGenerateParM comp !sz f = do
res <- sequence $ unsafePerformIO $ do
let !(ksz, szL) = unconsSz sz
!k = unSz ksz
withScheduler comp $ \ scheduler ->
splitLinearly (numWorkers scheduler) k $ \ chunkLength slackStart -> do
loopA_ 0 (< slackStart) (+ chunkLength) $ \ !start ->
scheduleWork scheduler $ do
res <- loopDeepM start (< (start + chunkLength)) (+ 1) [] $ \i acc ->
return (fmap lData (generateRaggedM Seq szL (\ !ixL -> f (consDim i ixL))):acc)
return $! sequence res
when (slackStart < k) $
scheduleWork scheduler $ do
res <- loopDeepM slackStart (< k) (+ 1) [] $ \i acc ->
return (fmap lData (generateRaggedM Seq szL (\ !ixL -> f (consDim i ixL))):acc)
return $! sequence res
return $ LArray comp $ List $ concat res
{-# INLINE unsafeGenerateParM #-}
instance Strategy L where
setComp c arr = arr {lComp = c}
{-# INLINE setComp #-}
getComp = lComp
{-# INLINE getComp #-}
repr = L
-- -- TODO: benchmark against using unsafeGenerateM directly
-- unsafeGenerateN ::
-- ( Ragged r ix e
-- , Ragged r (Lower ix) e
-- , Elt r ix e ~ Array r (Lower ix) e )
-- => Comp
-- -> Sz ix
-- -> (ix -> e)
-- -> Array r ix e
-- unsafeGenerateN comp sz f = unsafePerformIO $ do
-- let !(m, szL) = unconsSz sz
-- xs <- withScheduler comp $ \scheduler ->
-- loopM_ 0 (< coerce m) (+ 1) $ \i -> scheduleWork scheduler $
-- generateRaggedM comp szL $ \ix -> return $ f (consDim i ix)
-- return $! foldr' consR (emptyR comp) xs
-- {-# INLINE unsafeGenerateN #-}
-- | Construct an array backed by linked lists from any source array
--
-- @since 0.4.0
toListArray :: (Ragged L ix e, Shape r ix, Source r e) => Array r ix e -> Array L ix e
toListArray !arr = makeArray (getComp arr) (outerSize arr) (unsafeIndex arr)
{-# INLINE toListArray #-}
instance (Ragged L ix e, Show e) => Show (Array L ix e) where
showsPrec n arr = showsArrayLAsPrec (Proxy :: Proxy L) (outerSize arr) n arr
instance (Ragged L ix e, Show e) => Show (List ix e) where
show xs = " " ++ raggedFormat show "\n " arrL
where arrL = LArray Seq xs :: Array L ix e
showN :: (String -> a -> String) -> String -> [a] -> String
showN _ _ [] = "[ ]"
showN fShow lnPrefix ls =
L.concat
(["[ "] ++
L.intersperse (lnPrefix ++ ", ") (map (fShow (lnPrefix ++ " ")) ls) ++ [lnPrefix, "]"])
showsArrayLAsPrec ::
forall r ix e. (Ragged L ix e, Typeable r, Show e)
=> Proxy r
-> Sz ix
-> Int
-> Array L ix e -- Array to show
-> ShowS
showsArrayLAsPrec pr sz n arr =
opp .
("Array " ++) .
showsTypeRep (typeRep pr) .
(' ':) .
showsPrec 1 (getComp arr) . (" (" ++) . shows sz . (")\n" ++) . shows lnarr . clp
where
(opp, clp) =
if n == 0
then (id, id)
else (('(':), ("\n)" ++))
lnarr = lData arr
-- | Helper function for declaring `Show` instances for arrays
--
-- @since 0.4.0
showsArrayPrec ::
forall r r' ix e. (Ragged L ix e, Load r ix e, Load r' ix e, Source r' e, Show e)
=> (Array r ix e -> Array r' ix e) -- ^ Modifier
-> Int
-> Array r ix e -- Array to show
-> ShowS
showsArrayPrec f n arr = showsArrayLAsPrec (Proxy :: Proxy r) sz n larr
where
sz = size arr'
arr' = f arr
larr = makeArray (getComp arr') sz (evaluate' arr') :: Array L ix e
-- | Helper function for declaring `Show` instances for arrays
--
-- @since 0.4.0
showArrayList
:: Show arr => [arr] -> String -> String
showArrayList arrs = ('[':) . go arrs . (']':)
where
go [] = id
go [x] = (' ':) . shows x . ('\n':)
go (x:xs) = (' ':) . shows x . ("\n," ++) . go xs
instance Stream L Ix1 e where
toStream = S.fromList . unList . lData
{-# INLINE toStream #-}
toStreamIx = S.indexed . S.fromList . unList . lData
{-# INLINE toStreamIx #-}