simple-actors-0.1.0: A library for more structured concurrent programming, based on the Actor Model





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
>>> t <- spawn nil
>>> query t 7
>>> insert t 7
>>> query t 7

Actor Behaviors

data Action i a Source

In the Actor Model, at each step an actor...

  • processes a single received message
  • may spawn new actors
  • may send messages to other actors
  • returns 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 Behavior i Source

An actor is created by spawning 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.




headAction :: Action i (Behavior i)


Contravariant Behavior 
Monoid (Behavior i)

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.

Composing Behaviors

(<.|>) :: Behavior i -> Behavior i -> Behavior iSource

Sequence two Behaviors. After the first yields the second takes over, discarding the message the former was processing. See also the Monoid instance for Behavior.

 b <.|> b' = b `mappend` constB b'

Available actions

Message passing

data Mailbox a Source

One can send a messages to a Mailbox where it will be processed according to an actor's defined Behavior

send :: (MonadIO m, SplitChan c x) => c a -> a -> m ()Source

received :: Action i iSource

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

guardReceived :: (i -> Bool) -> Action i iSource

Return received message matching predicate, otherwise yield.

 guardReceived p = ask >>= \i-> guard (p i) >> return i

Spawning actors

spawn :: MonadIO m => Behavior i -> m (Mailbox i)Source

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 () -> m ()Source

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.

spawnReading :: (MonadIO m, SplitChan x c) => c i -> Behavior i -> m ()Source

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.

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 :: Action i aSource

Immediately give up processing an input, perhaps relinquishing the input to an Alternative computation or exiting the actor.

 yield = mzero

receive :: Action i (Behavior i) -> Action i (Behavior i)Source

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

Utility functions

runBehavior_ :: Behavior () -> IO ()Source

Run a Behavior () in the main thread, returning when the computation exits.

runBehavior :: Behavior a -> [a] -> IO ()Source

run a Behavior in the IO monad, taking its "messages" from the list. Useful for debugging Behaviors.

Useful predefined Behaviors

printB :: (Show s, Num n) => n -> Behavior sSource

Prints all messages to STDOUT in the order they are received, yield-ing immediately after n inputs are printed.

putStrB :: Num n => n -> Behavior StringSource

Like printB but using putStr.

signalB :: SplitChan c x => c () -> Behavior iSource

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)

constB :: Behavior i -> Behavior iSource

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