simple-actors 0.2.1 → 0.3.0
raw patch · 3 files changed
+163/−98 lines, 3 filesdep ~basedep ~chan-splitdep ~transformersPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base, chan-split, transformers
API changes (from Hackage documentation)
+ Control.Concurrent.Actors: (<->) :: (MonadIO m, SplitChan c x) => a -> m (c a) -> m (c a)
+ Control.Concurrent.Actors: coproductMb :: Mailbox a -> Mailbox b -> Mailbox (Either a b)
+ Control.Concurrent.Actors: faninMb :: (a -> c) -> (b -> c) -> Mailbox c -> Mailbox (Either a b)
+ Control.Concurrent.Actors: fanoutMb :: (a -> b) -> (a -> c) -> Mailbox (b, c) -> Mailbox a
+ Control.Concurrent.Actors: productMb :: Mailbox (Either a b) -> (Mailbox a, Mailbox b)
+ Control.Concurrent.Actors: send' :: (MonadIO m, SplitChan c x) => c a -> a -> m ()
+ Control.Concurrent.Actors: zipMb :: Mailbox a -> Mailbox b -> Mailbox (a, b)
Files
- Control/Concurrent/Actors.lhs +129/−73
- Control/Concurrent/Actors/Behavior.lhs +21/−19
- simple-actors.cabal +13/−6
Control/Concurrent/Actors.lhs view
@@ -1,12 +1,12 @@-> {-# LANGUAGE CPP, GeneralizedNewtypeDeriving, MultiParamTypeClasses #-}+> {-# LANGUAGE GeneralizedNewtypeDeriving, MultiParamTypeClasses #-} This module exports a simple, idiomatic implementation of the Actor Model. > module Control.Concurrent.Actors ( > > {- | -> Here we demonstrate a binary tree of actors that supports insert and query-> operations:+> Here we demonstrate a binary tree of actors that supports concurrent+> insert and query operations: > > > import Control.Concurrent.Actors > > import Control.Applicative@@ -68,38 +68,15 @@ > -} > > -- * Actor Behaviors-> {- | -> 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.-> -} > Action()-> {- | -> 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.-> -} > , Behavior(..)-> -- ** Composing Behaviors+> -- ** Composing and Transforming Behaviors > , (<.|>) > > -- * Available actions > -- ** Message passing > , Mailbox()-> , send+> , send , send' , (<->) > , received > , guardReceived > -- ** Spawning actors@@ -153,6 +130,17 @@ > , yield > , receive >+> -- ** Composing and Transforming Mailboxes+> {- |+> We offer some operations to split and combine 'Mailbox'es of sum and+> product types. +> -}+> , coproductMb+> , productMb+> , zipMb+> , faninMb+> , fanoutMb+> > -- * Utility functions > {- | > These are useful for debugging 'Behavior's@@ -174,6 +162,7 @@ > import Control.Monad.IO.Class > import Control.Concurrent(forkIO) > import Data.Monoid+> import Control.Arrow((***),(&&&),(|||)) > > -- from the contravariant package > import Data.Functor.Contravariant@@ -185,48 +174,42 @@ ------- CPP MACROS ------ -These macros are only provided by cabal unfortunately.... makes it difficult to-work with GHCi:+TODO+----- -#if !MIN_VERSION_base(4,3,0)-> void :: (Monad m)=> m a -> m ()-> void = (>> return ())-#endif+0.4+ - allow destructuring using UndecidableInstances (see mockup) on spawn, allowing for new, awesome synchronization semantics!+ - make that also work with Behaviors of arbitrary input types using new GHC generics! ------------------------- -TODO------- 0.3.0:- - define natural transformation combinators (in IO unfortunately) a.la.- 'categories' for Mailbox. So- - :: Mailbox (a,b) -> (Mailbox a, Mailbox b) -- divide?- - :: Mailbox a -> Mailbox b -> Mailbox (Either a b) -- add?- - etc...- put these in a separate sub-module, optionally import, mention how an- extension to actor model or something- - allow supplying the first input message for an actor during spawn. This is- awkward otherwise. Include in same sub-module as above?- - performance testing:+Later:+ - performance tuning / benchmarking:+ + look at interface file: ghc -ddump-hi Control/Concurrent/Actors.hs -O -c+ + remove current PRAGMA+ - close browser and everything, do a fake quick benchmark to get clock info+ - be more controlled about the source lists (do once before defaultMain), use 'evaluate'+ - run with +RTS -s and make sure everything is 0+ - see if case-based nil is better+ - get accurate baseline comparison between actors and set+ - use INLINABLE+ - test again with SPECIALIZE instead+ - try adding INLINE to all with higher-order args (or higher-order newtype wrappers)+ and make sure our LHS looks good for inlining+ - specialize `Action i (Behavior i)` or allow lots of unfolding... ? Optimize those loops, somehow. Rewrite rules? - take a look at threadscope for random tree test- - get complete code coverage into simple test module+ - look at "let floating" and INLINEABLE to get functions with "fully-applied (syntactically) LHS"+ - compare with previous version (cp to /tmp to use previous version)+ - get complete code coverage into simple test module - interesting solution to exit detection: http://en.wikipedia.org/wiki/Huang%27s_algorithm- - better method for waiting for threads to complete. should probably 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- - some sort of exception handling technique via Actors- (look at enumerator package)- - strict send' function--Later:+ - dynamically-bounded chans, based on number of writers to control+ producer/consumer issues? Possibly add more goodies to chan-split+ see: http://hackage.haskell.org/package/stm-chans+ - look at what Functor/Contravariant for read/write ends, and corresponding+ natural transformations those allow suggest about limits of Actor model+ and investigate inverse of Actors (Reducers?)+ - create an experimental Collectors sub-module - investigate ways of positively influencing thread scheduling based on actor work agenda? - export some more useful Actors and global thingies@@ -236,7 +219,13 @@ - an actor that sends a random stream? - a pre-declared Mailbox for IO? - Eventualy:+ Eventually:+ - some sort of exception handling technique (using actors?)+ - abilty to launch an actor that automatically "replicates" if its chan needs more+ consumers. This should probably be restricted to an `Action i ()` that we+ repeat.+ - can we automatically throttle producers on an Actor system level,+ optimizing message flow with some algorithm? - provide an "adapter" for amazon SQS, allowing truly distributed message passing - investigate erlang-style selective receive (using Alternative?)@@ -256,32 +245,79 @@ (maybe letting us use useful enumerators) ...also now pipes, conduits, etc. etc. + - study ambient/join/fusion calculi for clues as to where it's really at + CHAN TYPES ========== +By defining our Mailbox as the bare "send" operation we get a very convenient+way of defining contravariant instance, without all the overhead we had before,+while ALSO now supporting some great natural transformations on Mailboxes &+Messages.++We use this newtype to get 'Contravariant' for free, possibly revealing other+insights:++> type Sender a = Op (IO ()) a+>+> mailbox :: (a -> IO ()) -> Mailbox a+> mailbox = Mailbox . Op+>+> runMailbox :: Mailbox a -> a -> IO ()+> runMailbox = getOp . sender+>+> mkMailbox :: InChan a -> Mailbox a+> mkMailbox = mailbox . writeChan+>+> mkMessages :: OutChan a -> Messages a+> mkMessages = Messages . readChan+> > -- | 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 }+> newtype Mailbox a = Mailbox { sender :: Sender a } > deriving (Contravariant)-> 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: -> newtype Messages a = Messages { outChan :: OutChan a }+> newtype Messages a = Messages { readMsg :: IO 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+> readChan = readMsg+> writeChan = runMailbox > > instance NewSplitChan Mailbox Messages where-> newSplitChan = fmap (\(i,o)-> (Mailbox i, Messages o)) newSplitChan+> newSplitChan = (mkMailbox *** mkMessages) `fmap` newSplitChan+++For Mailboxes we can define all transformations associated with Cartesian and +CoCartesian (from 'categories') but where the category is Dual (->), i.e. the+order of the transformation is flipped.++I don't know if/how these precisely fit into an existing class, but for now here+are a handful of useful combinators:++> coproductMb :: Mailbox a -> Mailbox b -> Mailbox (Either a b) +> coproductMb m1 m2 = mailbox $ either (writeChan m1) (writeChan m2) >+> zipMb :: Mailbox a -> Mailbox b -> Mailbox (a,b) +> zipMb m1 m2 = mailbox $ \(a,b) -> writeChan m1 a >> writeChan m2 b+>+> -- | > productMb = contramap Left &&& contramap Right+> productMb :: Mailbox (Either a b) -> (Mailbox a, Mailbox b)+> productMb = contramap Left &&& contramap Right+>+> -- | > faninMb f g = contramap (f ||| g)+> faninMb :: (a -> c) -> (b -> c)-> Mailbox c -> Mailbox (Either a b) +> faninMb f g = contramap (f ||| g)+>+> -- | > fanoutMb f g = contramap (f &&& g)+> fanoutMb :: (a -> b) -> (a -> c) -> Mailbox (b,c) -> Mailbox a+> fanoutMb f g = contramap (f &&& g) @@ -353,8 +389,25 @@ > send :: (MonadIO m, SplitChan c x)=> c a -> a -> m () > send b = liftIO . writeChan b +> -- | A strict 'send':+> --+> -- > send' b a = a `seq` send b a+> send' :: (MonadIO m, SplitChan c x)=> c a -> a -> m ()+> send' b a = a `seq` send b a +> infixr 1 <->+>+> -- | Like 'send' but supports chaining sends by returning the Mailbox.+> -- Convenient for initializing an Actor with its first input after spawning,+> -- e.g.+> --+> -- > do mb <- 0 <-> spawn foo+> (<->) :: (MonadIO m, SplitChan c x)=> a -> m (c a) -> m (c a)+> a <-> mmb = mmb >>= \mb-> send mb a >> return mb +++ FORKING AND RUNNING ACTORS: =========================== @@ -389,8 +442,11 @@ -------------- > -- | 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_'.+> -- begins execution of its 'headBehavior' only after a message has been +> -- received; for sending an initial message to an actor right after 'spawn'ing+> -- it, ('<|>') can be convenient.+> --+> -- See also 'spawn_'. > spawn :: (MonadIO m)=> Behavior i -> m (Mailbox i) > spawn b = do > (m,s) <- liftIO newSplitChan
Control/Concurrent/Actors/Behavior.lhs view
@@ -1,9 +1,6 @@-> {-# LANGUAGE CPP, GeneralizedNewtypeDeriving #-}-> -- Since we don't get a MonadFix instance for MaybeT from transformers:-> {-# OPTIONS_GHC -fno-warn-orphans #-}+> {-# LANGUAGE GeneralizedNewtypeDeriving #-} > 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:@@ -29,6 +26,9 @@ A Behavior wraps an Action i a, into a list-like sequence of actions to perform over inputs: +> -- | An actor is created by 'Control.Concurrent.Actors.spawn'ing a @Behavior@. Behaviors consist of a+> -- composed 'Action' that is executed when a message is 'Control.Concurrent.Actors.received' and+> -- returns the @Behavior@ for processing the next input. > newtype Behavior i = Receive { headAction :: Action i (Behavior i) } > > instance Contravariant Behavior where@@ -39,7 +39,7 @@ 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 'yield'+> -- | @b1 `mplus` b2@ has the 'headAction' of @b2@ begin where the 'Control.Concurrent.Actors.yield' > -- occured in @b1@, i.e. @b2@\'s first input will be the final input handed to > -- @b1@. > instance Monoid (Behavior i) where@@ -54,24 +54,26 @@ will be useful: +> -- | In the Actor Model, at each step an actor...+> -- +> -- - processes a single 'Control.Concurrent.Actors.received' message+> -- +> -- - may 'Control.Concurrent.Actors.spawn' new actors+> -- +> -- - may 'Control.Concurrent.Actors.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:
simple-actors.cabal view
@@ -7,7 +7,7 @@ -- 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.2.1+Version: 0.3.0 -- A short (one-line) description of the package. Synopsis: A library for more structured concurrent programming, based@@ -21,10 +21,17 @@ single input value, perform some 'Action's, and return the Behavior to process the next input message it receives. .- /CHANGES/ 0.2.0 - 0.2.1:+ /CHANGES/ 0.2.1 - 0.3: .- - constrain version of chan-split+ - added (@\<-\>@) convenience operator, and strict @send'@+ .+ - add composition\/transformation functions for Mailboxes+ .+ - drop support for older base and transformers, no more CPP+ .+ - depend on chan-split 0.4 + -- The license under which the package is released. License: BSD3 @@ -64,10 +71,10 @@ Exposed-modules: Control.Concurrent.Actors -- Packages needed in order to build this package.- Build-depends: base >= 4 && < 5 - , chan-split <= 0.3.0+ Build-depends: base >= 4.3 && < 5 + , chan-split >= 0.4.0 , mtl >= 2- , transformers+ , transformers >= 0.3 , contravariant