simple-actors (empty) → 0.0.1
raw patch · 4 files changed
+310/−0 lines, 4 filesdep +basedep +transformerssetup-changed
Dependencies added: base, transformers
Files
- Control/Concurrent/Actors.lhs +215/−0
- LICENSE +30/−0
- Setup.hs +3/−0
- simple-actors.cabal +62/−0
+ Control/Concurrent/Actors.lhs view
@@ -0,0 +1,215 @@+> {-# LANGUAGE GeneralizedNewtypeDeriving, ViewPatterns #-}++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+> , send+> -- ** Actor system output:+> , receive+> , receiveList+> -- ** Mailbox+> , Mailbox()+> , newMailbox+> -- * Running Actors+> , Action()+> , forkActor+> , forkActorUsing+> , forkLoop+> , runActorUsing+> , runLoop+> ) where+>+> import Control.Monad+> import Control.Monad.IO.Class+> import Control.Monad.Trans.Maybe+> import Control.Concurrent+> import Control.Applicative++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 ++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.++> -- | 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++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)+++> 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.++> -- | Actor terminating:+> done :: ActorM (NextActor i)+> done = mzero++ 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++> -- | 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+++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 ()++++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.++ 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++ 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 buffered message passing medium used between actors+> newtype Mailbox i = Mailbox { mailbox :: Chan i }+>++ 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+++> -- | 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)++> -- | 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+++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++> -- | 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+>+> instance Action ActorM where+> liftIOtoA = ActorM . liftIO+++++> -- | 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 +>+> -- | run a Loop actor in the main thread, returning when the computation exits+> runLoop :: Loop -> IO ()+> runLoop l = runActorM l >>= +> maybe (return ()) (runLoop . ($ ()) . nextActor)++>+> -- | 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.++> actorHandler :: Mailbox i -> Actor i -> IO ()+> actorHandler (mailbox->c) = loop+> where loop a = readChan c >>= +> runActorM . a >>= +> maybe (return ()) (loop . nextActor)+
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Brandon Simmons 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 Brandon Simmons 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
+ simple-actors.cabal view
@@ -0,0 +1,62 @@+-- simple-actors.cabal auto-generated by cabal init. For additional+-- options, see+-- http://www.haskell.org/cabal/release/cabal-latest/doc/users-guide/authors.html#pkg-descr.+-- The name of the package.+Name: simple-actors++-- 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++-- A short (one-line) description of the package.+Synopsis: A simple implementation of the actor model of concurrency++-- A longer description of the package+Description: Simple concurrency primitives based on the Actor Model.++-- The license under which the package is released.+License: BSD3++-- The file containing the license text.+License-file: LICENSE++-- The package author(s).+Author: Brandon Simmons++-- An email address to which users can send suggestions, bug reports,+-- and patches.+Maintainer: brandon.m.simmons@gmail.com++-- A copyright notice.+-- Copyright: ++-- Stability of the pakcage (experimental, provisional, stable...)+Stability: Experimental++Category: Concurrency++Build-type: Simple++-- Extra files to be distributed with the package, such as examples or+-- a README.+-- Extra-source-files: ++-- Constraint on the version of Cabal needed to build this package.+Cabal-version: >=1.2+++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 + + -- Modules not exported by this package.+ -- Other-modules: + + -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source.+ -- Build-tools: +