monad-par-extras (empty) → 0.3
raw patch · 8 files changed
+791/−0 lines, 8 filesdep +abstract-pardep +basedep +cerealsetup-changed
Dependencies added: abstract-par, base, cereal, deepseq, mtl, random, transformers
Files
- Control/Monad/Par/AList.hs +285/−0
- Control/Monad/Par/Combinator.hs +183/−0
- Control/Monad/Par/Pedigree.hs +49/−0
- Control/Monad/Par/RNG.hs +64/−0
- Control/Monad/Par/State.hs +116/−0
- LICENSE +30/−0
- Setup.hs +3/−0
- monad-par-extras.cabal +61/−0
+ Control/Monad/Par/AList.hs view
@@ -0,0 +1,285 @@+{-# LANGUAGE CPP, DeriveDataTypeable #-}+{-# 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 +{-# DEPRECATED "This structure does not perform well, and will be removed in future versions" #-}+ (+ -- * The 'AList' type and operations+ AList(..),+ empty, singleton, cons, head, tail, length, null, append,+ toList, fromList, fromListBalanced, ++ -- * Regular (non-parallel) Combinators+ filter, map, partition,++ -- * Operations to build 'AList's in the 'Par' monad+ parBuildThresh, parBuildThreshM,+ parBuild, parBuildM,++ -- * Inspect and modify the internal structure of an AList tree + depth, balance+ )+where ++import Control.DeepSeq+import Prelude hiding (length,head,tail,null,map,filter)+import qualified Prelude as P+import qualified Data.List as L+import qualified Control.Monad.Par.Combinator as C+import Control.Monad.Par.Class+import Data.Typeable+import qualified Data.Serialize as S++----------------------------------------------------------------------------------------------------++-- | List that support constant-time append (sometimes called+-- join-lists).+data AList a = ANil | ASing a | Append (AList a) (AList a) | AList [a]+ deriving (Typeable)++-- TODO -- Add vectors.++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++instance Show a => Show (AList a) where + show al = "fromList "++ show (toList al)++-- TODO: Better Serialization+instance S.Serialize a => S.Serialize (AList a) where+ put al = S.put (toList al)+ get = do x <- S.get + return (fromList x)++++----------------------------------------------------------------------------------------------------++{-# INLINE append #-}+-- | /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 #-}+-- | /O(1)/ an empty 'AList'+empty :: AList a+empty = ANil++{-# INLINE singleton #-}+-- | /O(1)/ a singleton 'AList'+singleton :: a -> AList a+singleton = ASing++{-# INLINE fromList #-}+-- | /O(1)/ convert an ordinary list to an 'AList'+fromList :: [a] -> AList a+fromList = AList++-- | Convert an ordinary list, but do so using 'Append' and+-- 'ASing' rather than 'AList'+fromListBalanced :: [a] -> AList a+fromListBalanced xs = go xs (P.length xs)+ where + go _ 0 = ANil+ go ls 1 = case ls of + (h:_) -> ASing h+ [] -> error "the impossible happened"+ go ls n = + let (q,r) = quotRem n 2 in+ Append (go ls q)+ (go (drop q ls) (q+r))+++-- | Balance the tree representation of an AList. +balance :: AList a -> AList a+balance = fromListBalanced . toList+-- This would be much better if ALists tracked their size.++{-# INLINE cons #-}+-- | /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 ++{-# INLINE null #-}+-- | /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++partition :: (a -> Bool) -> AList a -> (AList a, AList a)+partition p a = go a (ANil, ANil)+ where go ANil acc = acc+ go (ASing a) (ys, ns) | p a = (a `cons` ys, ns)+ go (ASing a) (ys, ns) | otherwise = (ys, a `cons` ns)+ go (Append l r) acc = go l $! go r acc+ go (AList xs) (ys, ns) = (AList ys' `append` ys, AList ns' `append` ns)+ where+ (ys', ns') = L.partition p xs++depth :: AList a -> Int+depth ANil = 0+depth (ASing _) = 1+depth (AList _) = 1+depth (Append l r) = 1 + max (depth l) (depth r)+++-- The filter operation compacts dead space in the tree that would be+-- left by ANil nodes.+filter :: (a -> Bool) -> AList a -> AList a+filter p l = loop l + where + loop ANil = ANil+ loop o@(ASing x) = if p x then o else ANil+ loop (AList ls) = AList$ P.filter p ls+ loop (Append x y) = + let l = loop x+ r = loop y in+ case (l,r) of + (ANil,ANil) -> ANil+ (ANil,y) -> y+ (x,ANil) -> x+ (x,y) -> Append x y++-- | The usual `map` operation.+map :: (a -> b) -> AList a -> AList b+map _ ANil = ANil +map f (ASing x) = ASing (f x)+map f (AList l) = AList (P.map f l)+map f (Append x y) = Append (map f x) (map f y)+++--------------------------------------------------------------------------------+-- * Combinators built on top of a Par monad.++-- | 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)++-- | 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, ParFuture f p) => Int -> C.InclusiveRange -> (Int -> a) -> p (AList a)+parBuildThresh threshold range fn =+ C.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, ParFuture f p) => Int -> C.InclusiveRange -> (Int -> p a) -> p (AList a)+parBuildThreshM threshold range fn =+ C.parMapReduceRangeThresh threshold range + (\x -> fn x >>= return . singleton) 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, ParFuture f p) => C.InclusiveRange -> (Int -> a) -> p (AList a)+parBuild range fn =+ C.parMapReduceRange range (return . singleton . fn) appendM empty++-- | like 'parBuild', but the construction function is monadic+parBuildM :: (NFData a, ParFuture f p) => C.InclusiveRange -> (Int -> p a) -> p (AList a)+parBuildM range fn =+ C.parMapReduceRange range (\x -> fn x >>= return . singleton) appendM empty++--------------------------------------------------------------------------------++-- TODO: Provide a strategy for @par@-based maps:++-- TODO: tryHead -- returns Maybe++-- TODO: headTail -- returns head and tail, +-- i.e. if we're doing O(N) work, don't do it twice.++-- FIXME: Could be more efficient:+instance Eq a => Eq (AList a) where+ a == b = toList a == toList b ++-- 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+++--------------------------------------------------------------------------------+-- Internal helpers:++appendM :: ParFuture f p => AList a -> AList a -> p (AList a)+appendM x y = return (append x y)
+ Control/Monad/Par/Combinator.hs view
@@ -0,0 +1,183 @@+{-# LANGUAGE BangPatterns #-}+{-| + A collection of useful parallel combinators based on top of a 'Par' monad.++ In particular, this module provides higher order functions for+ traversing data structures in parallel. ++-}++module Control.Monad.Par.Combinator+ (+ parMap, parMapM,+ parMapReduceRangeThresh, parMapReduceRange,+ InclusiveRange(..),+ parFor+ )+where ++import Control.DeepSeq+import Data.Traversable+import Control.Monad as M hiding (mapM, sequence, join)+import Prelude hiding (mapM, sequence, head,tail)+import GHC.Conc (numCapabilities)++import Control.Monad.Par.Class++-- -----------------------------------------------------------------------------+-- Parallel maps over Traversable data structures++-- | Applies the given function to each element of a data structure+-- in parallel (fully evaluating the results), and returns a new data+-- structure containing the results.+--+-- > parMap f xs = mapM (spawnP . f) xs >>= mapM get+--+-- @parMap@ is commonly used for lists, where it has this specialised type:+--+-- > parMap :: NFData b => (a -> b) -> [a] -> Par [b]+--+parMap :: (Traversable t, NFData b, ParFuture iv p) => (a -> b) -> t a -> p (t b)+parMap f xs = mapM (spawnP . f) xs >>= mapM get++-- | Like 'parMap', but the function is a @Par@ monad operation.+--+-- > parMapM f xs = mapM (spawn . f) xs >>= mapM get+--+parMapM :: (Traversable t, NFData b, ParFuture iv p) => (a -> p b) -> t a -> p (t b)+parMapM f xs = mapM (spawn . f) xs >>= mapM get++-- TODO: parBuffer++++-- --------------------------------------------------------------------------------++-- TODO: Perhaps should introduce a class for the "splittable range" concept.+data InclusiveRange = InclusiveRange Int Int++-- | Computes a binary map\/reduce over a finite range. The range is+-- recursively split into two, the result for each half is computed in+-- parallel, and then the two results are combined. When the range+-- reaches the threshold size, the remaining elements of the range are+-- computed sequentially.+--+-- For example, the following is a parallel implementation of+--+-- > foldl (+) 0 (map (^2) [1..10^6])+--+-- > parMapReduceRangeThresh 100 (InclusiveRange 1 (10^6))+-- > (\x -> return (x^2))+-- > (\x y -> return (x+y))+-- > 0+--+parMapReduceRangeThresh+ :: (NFData a, ParFuture iv p)+ => Int -- ^ threshold+ -> InclusiveRange -- ^ range over which to calculate+ -> (Int -> p a) -- ^ compute one result+ -> (a -> a -> p a) -- ^ combine two results (associative)+ -> a -- ^ initial result+ -> p a++parMapReduceRangeThresh threshold (InclusiveRange min max) fn binop init+ = loop min max+ where+ loop min max+ | max - min <= threshold =+ let mapred a b = do x <- fn b;+ result <- a `binop` x+ return result+ in foldM mapred init [min..max]++ | otherwise = do+ let mid = min + ((max - min) `quot` 2)+ rght <- spawn $ loop (mid+1) max+ l <- loop min mid+ r <- get rght+ l `binop` r++-- How many tasks per process should we aim for? Higher numbers+-- improve load balance but put more pressure on the scheduler.+auto_partition_factor :: Int+auto_partition_factor = 4++-- | \"Auto-partitioning\" version of 'parMapReduceRangeThresh' that chooses the threshold based on+-- the size of the range and the number of processors..+parMapReduceRange :: (NFData a, ParFuture iv p) => + InclusiveRange -> (Int -> p a) -> (a -> a -> p a) -> a -> p a+parMapReduceRange (InclusiveRange start end) fn binop init =+ loop (length segs) segs+ where+ segs = splitInclusiveRange (auto_partition_factor * numCapabilities) (start,end)+ loop 1 [(st,en)] =+ let mapred a b = do x <- fn b;+ result <- a `binop` x+ return result+ in foldM mapred init [st..en]+ loop n segs =+ let half = n `quot` 2+ (left,right) = splitAt half segs in+ do l <- spawn$ loop half left+ r <- loop (n-half) right+ l' <- get l+ l' `binop` r+++-- TODO: A version that works for any splittable input domain. In this case+-- the "threshold" is a predicate on inputs.+-- parMapReduceRangeGeneric :: (inp -> Bool) -> (inp -> Maybe (inp,inp)) -> inp ->+++-- Experimental:++-- | Parallel for-loop over an inclusive range. Semantically equivalent+-- to+-- +-- > parFor (InclusiveRange n m) f = forM_ [n..m] f+--+-- except that the implementation will split the work into an+-- unspecified number of subtasks in an attempt to gain parallelism.+-- The exact number of subtasks is chosen at runtime, and is probably+-- a small multiple of the available number of processors.+--+-- Strictly speaking the semantics of 'parFor' depends on the+-- number of processors, and its behaviour is therefore not+-- deterministic. However, a good rule of thumb is to not have any+-- interdependencies between the elements; if this rule is followed+-- then @parFor@ has deterministic semantics. One easy way to follow+-- this rule is to only use 'put' or 'put_' in @f@, never 'get'.++parFor :: (ParFuture iv p) => InclusiveRange -> (Int -> p ()) -> p ()+parFor (InclusiveRange start end) body =+ do+ let run (x,y) = for_ x (y+1) body+ range_segments = splitInclusiveRange (4*numCapabilities) (start,end)++ vars <- M.forM range_segments (\ pr -> spawn_ (run pr))+ M.mapM_ get vars+ return ()++splitInclusiveRange :: Int -> (Int, Int) -> [(Int, Int)]+splitInclusiveRange pieces (start,end) =+ map largepiece [0..remain-1] +++ map smallpiece [remain..pieces-1]+ where+ len = end - start + 1 -- inclusive [start,end]+ (portion, remain) = len `quotRem` pieces+ largepiece i =+ let offset = start + (i * (portion + 1))+ in (offset, offset + portion)+ smallpiece i =+ let offset = start + (i * portion) + remain+ in (offset, offset + portion - 1)++-- My own forM for numeric ranges (not requiring deforestation optimizations).+-- Inclusive start, exclusive end.+{-# INLINE for_ #-}+for_ :: Monad m => Int -> Int -> (Int -> m ()) -> m ()+for_ start end _fn | start > end = error "for_: start is greater than end"+for_ start end fn = loop start+ where+ loop !i | i == end = return ()+ | otherwise = do fn i; loop (i+1)
+ Control/Monad/Par/Pedigree.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE TypeSynonymInstances, CPP, FlexibleInstances, BangPatterns #-}++-- | This module extends a Par monad with /pedigree/. That is, it+-- allows a running computation to look up its position in the+-- dynamic binary tree of `fork` calls ("ancestry").++module Control.Monad.Par.Pedigree+ (+ pedigree, ParPedigreeT+ , unpack, runParPedigree+ ) + where ++import Control.Monad.Par.Class+import Control.Monad.Par.State+import Control.Monad.Trans.State.Strict as S ++-- It's running slightly better with normal lists for parfib:+#if 0 +import Data.BitList+type BList = BitList+#else+type BList = [Bool]+unpack (Pedigree _ x) = x+cons = (:)+empty = []+#endif++type ParPedigreeT p a = S.StateT Pedigree p a++-- type Pedigree = BList+-- -- | Trivial instance.+-- instance SplittableState Pedigree where+-- splitState bl = (cons False bl, cons True bl)++data Pedigree = + Pedigree { ivarCounter :: {-# UNPACK #-} !Int, + treePath :: !BList }++instance SplittableState Pedigree where+ splitState (Pedigree cnt bl) = + (Pedigree cnt (cons False bl), + Pedigree cnt (cons True bl))++pedigree :: ParFuture iv p => S.StateT Pedigree p Pedigree+pedigree = S.get++runParPedigree :: Monad p => ParPedigreeT p a -> p a+runParPedigree m = S.evalStateT m (Pedigree 0 empty)
+ Control/Monad/Par/RNG.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}++-- | This module defines another Par-related class to capture the+-- random number generation capability. +-- +-- The `rand` operation provides deterministic parallel random+-- number generation from within a Par monad.+-- +-- Most likely one will simply use the `ParRand` the instance+-- provided in this file, which is based on a state transformer+-- carrying the random generator.+++module Control.Monad.Par.RNG + (+ ParRand(..), runParRand, ParRandStd+ ) where ++import System.Random+import Control.Exception++import Control.Monad.Par.Class+import Control.Monad.Par.State+import Control.Monad.Trans+import Control.Monad.Trans.State.Strict as S ++-- | A `ParRand` monad is a Par monad with support for random number generation..+class ParRand p where + rand :: Random a => p a+ -- This can be more efficient:+ randInt :: p Int+ randInt = rand ++-- | Trivial instance.+instance RandomGen g => SplittableState g where+ splitState = split++-- | The most straightforward way to get a `ParRand` monad: carry a+-- RNG in a state transformer.+instance (ParFuture fut p, RandomGen g) => ParRand (StateT g p) where + rand = do + g <- S.get+ let (x,g') = random g + S.put g'+ return x+ randInt = do + g <- S.get+ let (x,g') = next g+ S.put g'+ return x++-- An alternative is for these operators to be standalone without a class:+-- rand :: (ParFuture p fut, RandomGen g, Random a) => StateT g p a+-- randInt :: (ParFuture p fut, RandomGen g) => StateT g p Int++-- runParRand :: ParRand p => (p a -> a) -> p a -> IO a+runParRand :: ParFuture fut p => (p a -> a) -> StateT StdGen p a -> IO a+runParRand runPar m = + do g <- newStdGen+ evaluate (runPar (evalStateT m g))+++-- | A convenience type for the most standard+type ParRandStd par a = StateT StdGen par a
+ Control/Monad/Par/State.hs view
@@ -0,0 +1,116 @@+{-# LANGUAGE ScopedTypeVariables, FlexibleInstances, + MultiParamTypeClasses, UndecidableInstances, CPP+ #-}++-- | This module provides a notion of (Splittable) State that is+-- compatible with any Par monad.+++module Control.Monad.Par.State + (+ SplittableState(..)+ )+ where++import Control.Monad+import qualified Control.Monad.Par.Class as PC+import Control.Monad.Trans+import qualified Control.Monad.Trans.State.Strict as S+import qualified Control.Monad.Trans.State.Lazy as SL++---------------------------------------------------------------------------------+--- Make Par computations with state work.+--- (TODO: move these instances to a different module.)++-- | A type in `SplittableState` is meant to be added as to a Par monad+-- using StateT. It works like any other state except at `fork`+-- points, where the runtime system splits the state using `splitState`.+-- +-- Common examples for applications of `SplittableState` would+-- include (1) routing a splittable random number generator through+-- a parallel computation, and (2) keeping a tree-index that locates+-- the current computation within the binary tree of `fork`s.+class SplittableState a where+ splitState :: a -> (a,a)++----------------------------------------------------------------------------------------------------+-- Strict State:++-- | Adding State to a `ParFuture` monad yields another `ParFuture` monad.+instance (SplittableState s, PC.ParFuture fut p) + => PC.ParFuture fut (S.StateT s p) + where+ get = lift . PC.get+ spawn_ (task :: S.StateT s p ans) = + do s <- S.get + let (s1,s2) = splitState s+ S.put s2 -- Parent comp. gets one branch.+ lift$ PC.spawn_ $ S.evalStateT task s1 -- Child the other.++-- | Likewise, adding State to a `ParIVar` monad yield s another `ParIVar` monad.+instance (SplittableState s, PC.ParIVar iv p) + => PC.ParIVar iv (S.StateT s p) + where+ fork (task :: S.StateT s p ()) = + do s <- S.get + let (s1,s2) = splitState s+ S.put s2+ lift$ PC.fork $ do S.runStateT task s1; return ()++ new = lift PC.new+ put_ v x = lift$ PC.put_ v x+ newFull_ = lift . PC.newFull_++-- ParChan not released yet:+#if 0+-- | Likewise, adding State to a `ParChan` monad yield s another `ParChan` monad.+instance (SplittableState s, PC.ParChan snd rcv p) + => PC.ParChan snd rcv (S.StateT s p) + where+ newChan = lift PC.newChan+ recv r = lift $ PC.recv r+ send s x = lift $ PC.send s x+#endif+++----------------------------------------------------------------------------------------------------+-- Lazy State:++-- <DUPLICATE_CODE>++-- | Adding State to a `ParFuture` monad yield s another `ParFuture` monad.+instance (SplittableState s, PC.ParFuture fut p) + => PC.ParFuture fut (SL.StateT s p) + where+ get = lift . PC.get+ spawn_ (task :: SL.StateT s p ans) = + do s <- SL.get + let (s1,s2) = splitState s+ SL.put s2 -- Parent comp. gets one branch.+ lift$ PC.spawn_ $ SL.evalStateT task s1 -- Child the other.++-- | Likewise, adding State to a `ParIVar` monad yield s another `ParIVar` monad.+instance (SplittableState s, PC.ParIVar iv p) + => PC.ParIVar iv (SL.StateT s p) + where+ fork (task :: SL.StateT s p ()) = + do s <- SL.get + let (s1,s2) = splitState s+ SL.put s2+ lift$ PC.fork $ do SL.runStateT task s1; return ()++ new = lift PC.new+ put_ v x = lift$ PC.put_ v x+ newFull_ = lift . PC.newFull_++#if 0+-- | Likewise, adding State to a `ParChan` monad yield s another `ParChan` monad.+instance (SplittableState s, PC.ParChan snd rcv p) + => PC.ParChan snd rcv (SL.StateT s p)+ where+ newChan = lift PC.newChan+ recv r = lift $ PC.recv r+ send s x = lift $ PC.send s x+#endif++-- </DUPLICATE_CODE>
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Simon Marlow 2011++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Simon Marlow nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+import Distribution.Simple+main = defaultMain
+ monad-par-extras.cabal view
@@ -0,0 +1,61 @@+Name: monad-par-extras+Version: 0.3+Synopsis: Combinators and extra features for Par monads+++-- Version history:+-- 0.3 : Factored/reorganized modules. This module is a spinoff of +-- the original monad-par+++Description: The modules below provide additional+ data structures, and other added capabilities+ layered on top of the 'Par' monad.++ * Finish These+ * Module Descriptions++Homepage: https://github.com/simonmar/monad-par+License: BSD3+License-file: LICENSE+Author: Simon Marlow+Maintainer: Simon Marlow <marlowsd@gmail.com>+Copyright: (c) Simon Marlow 2011+Stability: Experimental+Category: Control,Parallelism,Monads+Build-type: Simple+Cabal-version: >=1.8++Library+ Exposed-modules: + -- A collection of combinators for common parallel+ -- patterns and data structure traversals:+ Control.Monad.Par.Combinator,++ -- Deprecated AList interface+ Control.Monad.Par.AList,++ -- State on top of Par is generally useful, but experimental+ Control.Monad.Par.State,+ + -- Deterministic RNG needs more testing.+ Control.Monad.Par.RNG++ Other-modules:+ -- Pedigree is experimental, but potentially useful for+ -- many purposes such as assigning unique, reproducable+ -- identifiers to IVars+ Control.Monad.Par.Pedigree+++ Build-depends: base >= 4 && < 5+ -- This provides the interface which monad-par implements:+ , abstract-par == 0.3.*+ , cereal == 0.3.*+ , deepseq == 1.3.* + , mtl == 2.0.*+ , random == 1.0.* + , transformers == 0.2.*++ ghc-options: -O2+ Other-modules: