simple-actors 0.0.1 → 0.1.0
raw patch · 3 files changed
+506/−167 lines, 3 filesdep +chan-splitdep +contravariantdep +mtl
Dependencies added: chan-split, contravariant, mtl
Files
- Control/Concurrent/Actors.lhs +344/−160
- Control/Concurrent/Actors/Behavior.lhs +139/−0
- simple-actors.cabal +23/−7
Control/Concurrent/Actors.lhs view
@@ -1,215 +1,399 @@-> {-# LANGUAGE GeneralizedNewtypeDeriving, ViewPatterns #-}+> {-# LANGUAGE CPP, GeneralizedNewtypeDeriving, MultiParamTypeClasses #-} This module exports a simple, idiomatic implementation of the Actor Model. > module Control.Concurrent.Actors (-> -- * Actor computations-> Actor-> , Loop-> , NextActor(..)-> , ActorM()-> -- ** Building Actors-> , continue-> , continue_-> , done-> , aseq-> -- * Message passing and IO+>+> {- | +> Here we demonstrate a binary tree of actors that supports insert and query+> operations:+> +> > import Control.Concurrent.Actors+> > import Control.Applicative+> > import Control.Concurrent.MVar+> > +> > -- the actor equivalent of a Nil leaf node:+> > nil :: Behavior Operation+> > nil = Receive $ do+> > (Query _ var) <- received +> > send var False -- signal Int is not present in tree+> > return nil -- await next message+> > +> > <|> do -- else, Insert received+> > l <- spawn nil -- spawn child nodes+> > r <- spawn nil+> > branch l r . val <$> received -- create branch from inserted val+> > +> > -- a branch node with a value 'v' and two children+> > branch :: Node -> Node -> Int -> Behavior Operation +> > branch l r v = loop where+> > loop = Receive $ do+> > m <- received +> > case compare (val m) v of+> > LT -> send l m+> > GT -> send r m+> > EQ -> case m of -- signal Int present in tree:+> > (Query _ var) -> send var True+> > _ -> return ()+> > return loop+> > +> > type Node = Mailbox Operation+> > +> > -- operations supported by the network:+> > data Operation = Insert { val :: Int }+> > | Query { val :: Int+> > , sigVar :: MVar Bool }+> > +> > insert :: Node -> Int -> IO ()+> > insert t = send t . Insert+> > +> > -- MVar is in the 'SplitChan' class so actors can 'send' to it:+> > query :: Node -> Int -> IO Bool+> > query t a = do+> > v <- newEmptyMVar+> > send t (Query a v)+> > takeMVar v+> +> You can use the tree defined above in GHCi:+> +> >>> :l TreeExample.hs +> Ok+> >>> t <- spawn nil+> >>> query t 7+> False+> >>> insert t 7+> >>> query t 7+> True+>+> -}+>+> -- * Actor Behaviors+> Action()+> , Behavior(..)+> -- ** Composing Behaviors+> , (<.|>)+>+> -- * Available actions+> -- ** Message passing+> , Mailbox() > , send-> -- ** Actor system output:+> , received+> , guardReceived+> -- ** Spawning actors+> {- | +> The 'spawn' function will be sufficient for forking actors in most cases,+> but launching mutually-communicating actors presents a problem.+> .+> In cases where a 'Behavior' needs access to its own 'Mailbox' or that of +> an actor that must be forked later, the 'MonadFix' instance should be+> used. GHC\'s \"Recursive Do\" make this especially easy:+> .+> > {-# LANGUAGE DoRec #-}+> > beh = Receive $ do+> > i <- received+> > -- similar to the scoping in a "let" block:+> > rec b1 <- spawn (senderTo b2)+> > b2 <- spawn (senderTo b1)+> > b3 <- spawn (senderTo b3)+> > -- send initial messages to actors spawned above:+> > send b3 i+> > send "first" b2+> > yield+> -}+> , spawn+> , spawn_+> , spawnReading+> -- ** Building an actor computation+> {- | +> An actor computation can be halted immediately by calling 'yield',+> a synonym for 'mzero'. When an 'Action' calling @yield@ is composed with+> another using @<|>@ the second takes over processing the /same/ input+> which the former @yield@-ed on.+>+> Here is an example of a computation using 'guard' which returns @mzero@ if+> the test is false:+>+> > foo c n = Receive $ +> > do i <- received+> > guard (n<10)+> > send c i+> > return (foo c $ n+1)+> >+> > <|> do i <- received -- same as the 'i' above+> > send c $ "TENTH INPUT: "++i+> > return (foo c 0)+>+> The @Monoid@ instance for 'Behavior' works on the same principle.+> -}+> , yield > , receive-> , receiveList-> -- ** Mailbox-> , Mailbox()-> , newMailbox-> -- * Running Actors-> , Action()-> , forkActor-> , forkActorUsing-> , forkLoop-> , runActorUsing-> , runLoop+>+> -- * Utility functions+> , runBehavior_+> , runBehavior +>+> -- * Useful predefined @Behavior@s+> , printB+> , putStrB+> , signalB+> , constB+> > ) where > > import Control.Monad+> import Control.Monad.Reader(ask)+> import qualified Data.Foldable as F > import Control.Monad.IO.Class-> import Control.Monad.Trans.Maybe-> import Control.Concurrent-> import Control.Applicative+> import Control.Concurrent(forkIO)+> import Data.Monoid+>+> -- from the contravariant package +> import Data.Functor.Contravariant+> -- from the chan-split package+> import Control.Concurrent.Chan.Split+>+> -- internal:+> import Control.Concurrent.Actors.Behavior -TODO?:- - Function for combining mailboxes (make a monoid? Only allow doing this in IO?) - Alternately: what if we delegated a single 'mainActor' that is in IO, and which- any Actor can send a message to? This would be an IO event loop and we would run- this IO loop from 'main' which would block until... so tired. - RE: Mailbox Class:- - Having such a class is a decent idea anyway since we may want to have- a synchronous Mailbox type, in which case we should +------ CPP MACROS ------ -Here we define the Actor environment, similar to IO, in which we can launch new-Actors and send messages to Actors in scope. The implementation is hidden from-the user to enforce these restrictions.+These macros are only provided by cabal unfortunately.... makes it difficult to+work with GHCi: -> -- | The Actor encironment in which Actors can be spawned and sent messages-> newtype ActorM a = ActorM { actorM :: MaybeT IO a }-> deriving (Monad, Functor, Applicative, -> Alternative, MonadPlus, MonadIO)->-> runActorM = runMaybeT . actorM+#if !MIN_VERSION_base(4,3,0)+> void :: (Monad m)=> m a -> m ()+> void = (>> return ())+#endif -First we define an Actor: a function that takes an input, maybe returning a new-actor:+------------------------ - TODO: Consider making Actor the newtype and eliminating NextActor- newtype Actor i = Actor { actor :: i -> ActorM (Actor i) }- continue :: (i -> ActorM (Actor i)) -> ActorM (Actor i)+TODO+----- + 0.2.0:+ - performance testing:+ - take a look at threadscope for random tree test+ - get complete code coverage into simple test module+ - interesting: http://en.wikipedia.org/wiki/Huang%27s_algorithm+ - better method for waiting for threads to complete. should probbly use+ actor message passing+ - look into whether we should use Text lib instead of strings?+ OverloadedStrings?+ -import Data.String, make polymorphic over IsString+ -test if this lets us use it in importing module w/ OverloadedStrings+ extension+ - structured declarative and unit tests+ - Performance testing:+ - test performance vs. straight Chans, etc.+ - test out overhead of our various locks, especially difference if we+ scrap the snederLockMutex+ - some sort of exception handling technique via Actors+ (look at enumerator package)+ - investigate ways of positively influencing thread scheduling based on+ actor work agenda + - strict send' function+ -Behavior -> enumeratee package translator (and vice versa)+ (maybe letting us use useful enumerators)+ - export some more useful Actors and global thingies+ - 'loop' which keeps consuming (is this provided by a class?)+ - function returning an actor to "load balance" inputs over multiple+ actors+ - an actor that sends a random stream?+ - a pre-declared Mailbox for IO?+ - provide an "adapter" for amazon SQS, allowing truly distributed message+ passing -> type Actor i = i -> ActorM (NextActor i)-> newtype NextActor i = NextActor { nextActor :: Actor i } -Now some functions for building Actor computations: -> -- | Continue with a new Actor computation step-> continue :: Actor i -> ActorM (NextActor i)-> continue = return . NextActor - IMPLEMENTATION NOTE: - when an actor terminates, its mailbox persists and we - currently provide no functions to query an actor's status. - Signaling an actor's termination should be done with - message passing.+CHAN TYPES+========== -> -- | Actor terminating:-> done :: ActorM (NextActor i)-> done = mzero+> -- | One can 'send' a messages to a @Mailbox@ where it will be processed+> -- according to an actor\'s defined 'Behavior'+> newtype Mailbox a = Mailbox { inChan :: InChan a }+> deriving (Contravariant)+> - IMPLEMENTATION NOTE: - We might find that we can use the monoid abstraction, or - that we should make Actor a newtype for other reasons. For- now we have this for composing+We don't need to expose this thanks to the miracle of MonadFix and recursive do,+but this can be generated via the NewSplitChan class below if the user imports+the library: -> -- | compose two actors. The second will take over when the first exits-> aseq :: Actor i -> Actor i -> Actor i-> aseq f g i = NextActor <$> (nextf <|> return g)-> where nextf = (`aseq` g) . nextActor <$> f i+> newtype Messages a = Messages { outChan :: OutChan a }+> deriving (Functor) +>+> -- Not sure how to derive this or if possible:+> instance SplitChan Mailbox Messages where+> readChan = readChan . outChan+> writeChan = writeChan . inChan+> writeList2Chan = writeList2Chan . inChan+>+> instance NewSplitChan Mailbox Messages where+> newSplitChan = fmap (\(i,o)-> (Mailbox i, Messages o)) newSplitChan+> -A Loop is just an Actor that ignores its input. We provide some useful-functions for building and running such computations: -> -- | An Actor that discards its input, i.e. a simple loop.-> type Loop = ActorM (NextActor ())->-> -- | Continue with a Loop computation-> continue_ :: Loop -> ActorM (NextActor i)-> continue_ = fmap (NextActor . fixConst . nextActor)-> where fixConst c = const $ continue_ $ c () +ACTIONS+======= +Functionality is based on our underlying type classes, but users shouldn't need+to import a bunch of libraries to get basic Behavior building functionality. -Here we define the "mailbox" that an Actor collects messages from, and other-actors send messages to. It is simply a Chan with hidden implementation.+> infixl 3 <.|> - IMPLEMENTATION NOTE: - we make no attempt to ensure that only one actor is reading - from a given Chan. This means two Actors can share the work - reading from the same mailbox.- - If we want to change this in the future, Mailbox will contain- a type :: TVar ThreadID+> -- | Sequence two @Behavior@s. After the first 'yield's the second takes over,+> -- discarding the message the former was processing. See also the 'Monoid'+> -- instance for @Behavior@.+> -- +> -- > b <.|> b' = b `mappend` constB b'+> (<.|>) :: Behavior i -> Behavior i -> Behavior i+> b <.|> b' = b `mappend` constB b' - To implement synchronous chans (or singly-buffered chans), - we can use a SyncMailbox type containing an MVar and- possibly another Var for ensuring syncronicity. An MVar- writer will never block indefinitely. Use a class for writing- and reading these mailbox types.+The 'yield' function is so named because it is "relinquishing control", i.e. I+think the name reminds of the functionality of <|> and mappend (the last input+is passed along) and also has the meaning "quit". - -> -- | the buffered message passing medium used between actors-> newtype Mailbox i = Mailbox { mailbox :: Chan i }+Its similarity (or not) to the 'enumerator' function of the same same may be a+source of confusion (or the opposite)... I'm not sure.++> -- | Immediately give up processing an input, perhaps relinquishing the input+> -- to an 'Alternative' computation or exiting the actor.+> -- +> -- > yield = mzero+> yield :: Action i a+> yield = mzero >+> -- | Useful to make defining a continuing Behavior more readable as a+> -- \"receive block\", e.g.+> --+> -- > pairUp out = Receive $ do+> -- > a <- received+> -- > receive $ do+> -- > b <- received+> -- > send out (b,a)+> -- > return (pairUp out)+> --+> -- Defined: @receive = return . Receive@+> receive :: Action i (Behavior i) -> Action i (Behavior i)+> receive = return . Receive - IMPLEMENTATION NOTE: - We allow sending of messages to Actors in IO, treating the - main thread as something of an Actor with special privileges;- It can launch actors and message them, but also read as it - pleases from Mailboxes+> -- | Return the message received to start this 'Action' block. /N.B/ the value+> -- returned here does not change between calls in the same 'Action'.+> --+> -- > received = ask+> received :: Action i i+> received = ask +> -- | Return 'received' message matching predicate, otherwise 'yield'.+> --+> -- > guardReceived p = ask >>= \i-> guard (p i) >> return i+> guardReceived :: (i -> Bool) -> Action i i+> guardReceived p = ask >>= \i-> guard (p i) >> return i -> -- | Send a message to an Actor. Actors can only be passed messages from other-> -- actors.-> send :: (Action m)=> Mailbox a -> a -> m ()-> send b = liftIOtoA . writeChan (mailbox b)+> -- | Send a message asynchronously. This can be used to send messages to other+> -- Actors via a 'Mailbox', or used as a means of output from the Actor system+> -- to IO since the function is polymorphic.+> -- .+> -- > send b = liftIO . writeChan b+> send :: (MonadIO m, SplitChan c x)=> c a -> a -> m ()+> send b = liftIO . writeChan b -> -- | Read a message from a mailbox in the IO monad. This can be used as the-> -- mechanism for output from an Actor system. Blocks if the actor is empty-> receive :: Mailbox o -> IO o-> receive = readChan . mailbox -> -- | Return a lazy list of mailbox contents-> receiveList :: Mailbox o -> IO [o]-> receiveList = getChanContents . mailbox -> -- | create a new mailbox that Actors can be launched to read from or-> -- send messages to in order to communicate with other actors-> newMailbox :: (Action m)=> m (Mailbox a)-> newMailbox = liftIOtoA newChan >>= return . Mailbox+FORKING AND RUNNING ACTORS:+=========================== -The Action class represents environments in which we can operate on actors. That-is we would like to be able to send a message in IO+> -- | Like 'spawn' but allows one to specify explicitly the channel from which+> -- an actor should take its input. Useful for extending the library to work+> -- over other channels.+> spawnReading :: (MonadIO m, SplitChan x c)=> c i -> Behavior i -> m ()+> spawnReading str = liftIO . void . forkIO . actorRunner +> where actorRunner b =+> readChan str >>= runBehaviorStep b >>= F.mapM_ actorRunner -> -- | monads in the Action class can participate in message passing and other-> -- Actor operations-> class Monad m => Action m where-> liftIOtoA :: IO a -> m a->-> forkA :: IO () -> m ()-> forkA io = liftIOtoA $ forkIO io >> return ()->-> instance Action IO where-> liftIOtoA = id++RUNNING ACTORS+--------------++These work in IO, returning () when the actor finishes with done/mzero:++> -- | Run a @Behavior ()@ in the main thread, returning when the computation+> -- exits.+> runBehavior_ :: Behavior () -> IO ()+> runBehavior_ b = runBehavior b [(),()..] >-> instance Action ActorM where-> liftIOtoA = ActorM . liftIO+> -- | run a 'Behavior' in the IO monad, taking its \"messages\" from the list.+> -- Useful for debugging @Behaviors@.+> runBehavior :: Behavior a -> [a] -> IO ()+> runBehavior b (a:as) = runBehaviorStep b a >>= F.mapM_ (`runBehavior` as)+> runBehavior _ _ = return () +FORKING ACTORS+-------------- -> -- | fork an actor, returning its mailbox-> forkActor :: (Action m)=> Actor i -> m (Mailbox i)-> forkActor a = do-> b <- newMailbox-> forkActorUsing b a-> return b-> -> -- | fork an actor that reads from the supplied Mailbox-> forkActorUsing :: (Action m)=> Mailbox i -> Actor i -> m ()-> forkActorUsing b = forkA . actorHandler b->-> -- | fork a looping computation which starts immediately-> forkLoop :: (Action m)=> Loop -> m ()-> forkLoop = forkA . runLoop +> -- | Fork an actor performing the specified 'Behavior'. /N.B./ an actor +> -- begins execution of its 'headBehavior' only after a mesage has been +> -- received. See also 'spawn_'.+> spawn :: (MonadIO m)=> Behavior i -> m (Mailbox i)+> spawn b = do+> (m,s) <- liftIO newSplitChan+> spawnReading s b+> return m >-> -- | run a Loop actor in the main thread, returning when the computation exits-> runLoop :: Loop -> IO ()-> runLoop l = runActorM l >>= -> maybe (return ()) (runLoop . ($ ()) . nextActor)+> -- | Fork a looping computation which starts immediately. Equivalent to+> -- launching a @Behavior ()@ and another 'Behavior' that sends an infinite stream of+> -- ()s to the former\'s 'Mailbox'.+> spawn_ :: (MonadIO m)=> Behavior () -> m ()+> spawn_ = liftIO . void . forkIO . runBehavior_ ->-> -- | run an Actor in the main thread, returning when the Actor exits-> runActorUsing :: Mailbox i -> Actor i -> IO ()-> runActorUsing = actorHandler -Internal function that feeds the actor computation its values. This may be-extended to support additional functionality in the future.+USEFUL GENERAL BEHAVIORS+======================== -> actorHandler :: Mailbox i -> Actor i -> IO ()-> actorHandler (mailbox->c) = loop-> where loop a = readChan c >>= -> runActorM . a >>= -> maybe (return ()) (loop . nextActor)+> -- | Prints all messages to STDOUT in the order they are received,+> -- 'yield'-ing /immediately/ after @n@ inputs are printed.+> printB :: (Show s, Num n)=> n -> Behavior s+> printB = contramap (unlines . return . show) . putStrB +We want to yield right after printing the last input to print. This lets us+compose with signalB for instance:++ write5ThenExit = putStrB 5 `mappend` signalB c++and the above will signal as soon as it has printed the last message. If we try+to define this in a more traditional recursive way the signal above would only+happen as soon as the sixth message was received.++For now we allow negative++> -- | Like 'printB' but using @putStr@.+> putStrB :: (Num n)=> n -> Behavior String+> putStrB 0 = mempty --special case when called directly w/ 0+> putStrB n = Receive $ do+> s <- received+> liftIO $ putStr s+> guard (n /= 1)+> return $ putStrB (n-1)++> -- | Sends a @()@ to the passed chan. This is useful with 'mappend' for+> -- signalling the end of some other 'Behavior'.+> --+> -- > signalB c = Receive (send c () >> yield)+> signalB :: (SplitChan c x)=> c () -> Behavior i+> signalB c = Receive (send c () >> yield)++> -- | A @Behavior@ that discard its first input, returning the passed Behavior+> -- for processing subsequent inputs. Useful with 'Alternative' or 'Monoid'+> -- compositions when one wants to ignore the leftover input.+> --+> -- > constB = Receive . return+> constB :: Behavior i -> Behavior i+> constB = Receive . return
+ Control/Concurrent/Actors/Behavior.lhs view
@@ -0,0 +1,139 @@+> {-# LANGUAGE CPP, GeneralizedNewtypeDeriving #-}+> -- Since we don't get a MonadFix instance for MaybeT from transformers:+> {-# OPTIONS_GHC -fno-warn-orphans #-}+> module Control.Concurrent.Actors.Behavior+> where+> ++Our main data types for actors and behaviors are defined in this private module.+We may expose this if it is useful to people:++These imports are mostly for all the instances for Action that we define:++> -- using 'mtl':+> import Control.Monad.Reader(ReaderT(..))+> import Control.Monad.Reader.Class+> import Control.Monad.Trans.Maybe+> import Control.Monad.IO.Class+> import Control.Monad.Fix+> import Control.Applicative+> import Control.Monad+>+> import Control.Arrow+> import qualified Control.Category as C+> import Data.Monoid+>+> import Data.Functor.Contravariant+++A Behavior wraps an Action i a, into a list-like sequence of actions to perform+over inputs:++> -- | An actor is created by 'spawn'ing a @Behavior@. Behaviors consist of+> -- a composed 'Action' that is executed when a message is 'received' and+> -- returns the @Behavior@ for processing the next input.+> newtype Behavior i = Receive { headAction :: Action i (Behavior i) }+> +> instance Contravariant Behavior where+> contramap f (Receive a) = Receive $ f ^>> (contramap f <$> a)+> --contramap f = Receive . withReaderT f . fmap (contramap f) . headAction+>++This is essentially a marriage of the Monoid [] instance with Action's+Alternative instance, and I am mostly convinced it is right and has utility:++> -- | @b1 `mplus` b2@ has the 'headAction' of @b2@ begin where the 'abort'+> -- occured in @b1@, i.e. @b2@\'s first input will be the final input handed to+> -- @b1@.+> instance Monoid (Behavior i) where+> mempty = Receive mzero+> mappend (Receive a1) b2@(Receive a2) = Receive $ +> -- is this the best way of defining this?:+> (flip mappend b2 <$> a1) <|> a2+++Defining Action as a Reader / Maybe stack lets us have a nice EDSL syntax for+constructing behaviors, and I am able to derive a bunch of the instances that+will be useful:+++> -- | In the Actor Model, at each step an actor...+> --+> -- - processes a single 'received' message+> -- +> -- - may 'spawn' new actors+> -- +> -- - may 'send' messages to other actors+> -- +> -- - 'return's the 'Behavior' for processing the /next/ message+> --+> -- These actions take place within the @Action i@ monad, where @i@ is the type+> -- of the input message the actor receives.+> --+> -- /N.B.:/ the MonadIO instance here is an abstraction leak. An example of a+> -- good use of 'liftIO' might be to give an @Action@ access to a source of+> -- randomness.+> newtype Action i a = Action { readerT :: ReaderT i (MaybeT IO) a }+> deriving (Monad, MonadIO, MonadPlus, MonadReader i,+> Functor, Applicative, Alternative, MonadFix)+> +++------ CPP MACROS ------++This should end up in the next version of 'transformers':+ http://www.haskell.org/pipermail/libraries/2011-April/016201.html++#if !MIN_VERSION_transformers(0,3,0)+> instance (MonadFix m) => MonadFix (MaybeT m) where+> mfix f = MaybeT $ mfix (runMaybeT . f . unJust)+> where unJust = maybe (error "mfix MaybeT: Nothing") id+#endif++------------------------+++Some helpers for wrapping / unwrapping:++> -- pack and unpack:+> runAction :: Action r a -> r -> MaybeT IO a+> runAction = runReaderT . readerT+> +> action :: (i -> MaybeT IO a) -> Action i a+> action = Action . ReaderT+> +> runBehaviorStep :: Behavior i -> i -> IO (Maybe (Behavior i))+> runBehaviorStep = fmap runMaybeT . runAction . headAction+++Kleisli is ReaderT, so these are basically cribbed from its instances. It's a+shame ReaderT's type arguments are the way they are or we could have derived+this:++> instance C.Category Action where+> id = ask+> f . g = action $ \i-> runAction g i >>= runAction f+> +> instance Arrow Action where+> arr = action . fmap return+> first f = action $ \ ~(b,d)-> runAction f b >>= \c -> return (c,d)+> +> -- additional Arrow sub-classes:+> instance ArrowApply Action where+> app = action (uncurry runAction)+> +> instance ArrowChoice Action where+> left f = f +++ C.id+> +> -- and following that MaybeT IO is a MonadPlus...+> instance ArrowPlus Action where+> f <+> g = action $ \i-> runAction f i `mplus` runAction g i+> +> instance ArrowZero Action where+> zeroArrow = action $ const mzero+> +> -- inspired by MonadFix instance. +> instance ArrowLoop Action where+> loop af = action (liftM fst . mfix . f')+> where f' x y = f (x, snd y)+> f = runAction af
simple-actors.cabal view
@@ -7,13 +7,19 @@ -- The package version. See the Haskell package versioning policy -- (http://www.haskell.org/haskellwiki/Package_versioning_policy) for -- standards guiding when and how versions should be incremented.-Version: 0.0.1+Version: 0.1.0 -- A short (one-line) description of the package.-Synopsis: A simple implementation of the actor model of concurrency+Synopsis: A library for more structured concurrent programming, based+ on the Actor Model+Homepage: http://coder.bsimmons.name/blog/2011/05/simple-actors-a-simple-actor-model-concurrency-library/ -- A longer description of the package-Description: Simple concurrency primitives based on the Actor Model.+Description: simple-actors is an EDSL-style library for writing+ more structured concurrent programs, based on the Actor + Model. Computations are structured as "Behaviors" which take a+ single input value, perform some 'Action's, and return the+ Behavior to process the next input message it receives. -- The license under which the package is released. License: BSD3@@ -43,19 +49,29 @@ -- Extra-source-files: -- Constraint on the version of Cabal needed to build this package.-Cabal-version: >=1.2+Cabal-version: >=1.6 +source-repository head + type: git+ location: https://github.com/jberryman/simple-actors.git Library -- Modules exported by the library. Exposed-modules: Control.Concurrent.Actors -- Packages needed in order to build this package.- Build-depends: transformers- , base >= 4 && < 5 + Build-depends: base >= 4 && < 5 + , chan-split+ , mtl >= 2+ , transformers+ , contravariant+ + ghc-options: -Wall+ -- Modules not exported by this package.- -- Other-modules: + Other-modules: Control.Concurrent.Actors.Behavior+ -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source. -- Build-tools: