{-# 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)