packages feed

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 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: