monad-par-0.1: Control/Monad/Par/AList.hs
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -Wall -fno-warn-name-shadowing -fwarn-unused-imports #-}
-- | This module defines the 'AList' type, a list that supports
-- constant-time append, and is therefore ideal for building the
-- result of tree-shaped parallel computations.
module Control.Monad.Par.AList
(
-- * The 'AList' type and operations
AList(..),
empty, singleton, cons, head, tail, length, null, append,
toList, fromList,
-- * Operations to build 'AList's in the 'Par' monad
parBuildThresh, parBuildThreshM,
parBuild, parBuildM,
)
where
import Control.DeepSeq
import Prelude hiding (length,head,tail,null)
import qualified Prelude as P
import Control.Monad.Par
-- | List that support constant-time append (sometimes called
-- join-lists).
data AList a = ANil | ASing a | Append (AList a) (AList a) | AList [a]
instance NFData a => NFData (AList a) where
rnf ANil = ()
rnf (ASing a) = rnf a
rnf (Append l r) = rnf l `seq` rnf r
rnf (AList l) = rnf l
#if 0
data Tree a = Empty | Leaf a | Node (Tree a) a (Tree a)
instance Traversable Tree
traverse f Empty = pure Empty
traverse f (Leaf x) = Leaf <$> f x
traverse f (Node l k r) = Node <$> traverse f l <*> f k <*> traverse f r
#endif
-- TODO: Finish me:
-- instance F.Foldable AList where
-- foldr fn init al =
-- case al of
-- ANil ->
-- instance Functor AList where
-- fmap = undefined
-- -- Walk the data structure without introducing any additional data-parallelism.
-- instance Traversable AList where
-- traverse f al =
-- case al of
-- ANil -> pure ANil
-- ASing x -> ASing <$> f x
instance Show a => Show (AList a) where
show al = "fromList "++ show (toList al)
-- | /O(1)/ Append two 'AList's
append :: AList a -> AList a -> AList a
append ANil r = r
append l ANil = l
append l r = Append l r
{-# INLINE empty #-}
{-# INLINE singleton #-}
{-# INLINE fromList #-}
-- | /O(1)/ an empty 'AList'
empty :: AList a
empty = ANil
-- | /O(1)/ a singleton 'AList'
singleton :: a -> AList a
singleton = ASing
-- | /O(1)/ convert an ordinary list to an 'AList'
fromList :: [a] -> AList a
fromList = AList
-- | /O(1)/ prepend an element
cons :: a -> AList a -> AList a
cons x ANil = ASing x
cons x al = Append (ASing x) al
-- If we tracked length perhaps this could make an effort at balance.
-- | /O(n)/ take the head element of an 'AList'
--
-- NB. linear-time, because the list might look like this:
--
-- > (((... `append` a) `append` b) `append` c)
--
head :: AList a -> a
head al =
case loop al of
Just x -> x
Nothing -> error "cannot take head of an empty AList"
where
-- Alas there are an infinite number of representations for null:
loop al =
case al of
Append l r -> case loop l of
x@(Just _) -> x
Nothing -> loop r
ASing x -> Just x
AList (h:_) -> Just h
AList [] -> Nothing
ANil -> Nothing
-- | /O(n)/ take the tail element of an 'AList'
tail :: AList a -> AList a
tail al =
case loop al of
Just x -> x
Nothing -> error "cannot take tail of an empty AList"
where
loop al =
case al of
Append l r -> case loop l of
(Just x) -> Just (Append x r)
Nothing -> loop r
ASing _ -> Just ANil
AList (_:t) -> Just (AList t)
AList [] -> Nothing
ANil -> Nothing
-- | /O(n)/ find the length of an 'AList'
length :: AList a -> Int
length ANil = 0
length (ASing _) = 1
length (Append l r) = length l + length r
length (AList l) = P.length l
-- | /O(n)/ returns 'True' if the 'AList' is empty
null :: AList a -> Bool
null = (==0) . length
-- | /O(n)/ converts an 'AList' to an ordinary list
toList :: AList a -> [a]
toList a = go a []
where go ANil rest = rest
go (ASing a) rest = a : rest
go (Append l r) rest = go l $! go r rest
go (AList xs) rest = xs ++ rest
-- TODO: Provide a strategy for @par@-based maps:
appendM :: AList a -> AList a -> Par (AList a)
appendM x y = return (append x y)
-- | Build a balanced 'AList' in parallel, constructing each element as a
-- function of its index. The threshold argument provides control
-- over the degree of parallelism. It indicates under what number
-- of elements the build process should switch from parallel to
-- serial.
parBuildThresh :: NFData a => Int -> InclusiveRange -> (Int -> a) -> Par (AList a)
parBuildThresh threshold range fn =
parMapReduceRangeThresh threshold range
(return . singleton . fn) appendM empty
-- | Variant of 'parBuildThresh' in which the element-construction function is itself a 'Par' computation.
parBuildThreshM :: NFData a => Int -> InclusiveRange -> (Int -> Par a) -> Par (AList a)
parBuildThreshM threshold range fn =
parMapReduceRangeThresh threshold range
((fmap singleton) . fn) appendM empty
-- | \"Auto-partitioning\" version of 'parBuildThresh' that chooses the threshold based on
-- the size of the range and the number of processors..
parBuild :: NFData a => InclusiveRange -> (Int -> a) -> Par (AList a)
parBuild range fn =
parMapReduceRange range (return . singleton . fn) appendM empty
-- | like 'parBuild', but the construction function is monadic
parBuildM :: NFData a => InclusiveRange -> (Int -> Par a) -> Par (AList a)
parBuildM range fn =
parMapReduceRange range ((fmap singleton) . fn) appendM empty
-- | A parMap over an AList can result in more balanced parallelism than
-- the default parMap over Traversable data types.
-- parMap :: NFData b => (a -> b) -> AList a -> Par (AList b)