{-# LANGUAGE TypeFamilies #-} {- | Description: Actor pattern utilities. -} module OM.Actor ( {- * Implementing an actor. -} {- | Types/functions in this section help you implement the actor iteself. -} Actor(..), Responder, Responded, respond, {- * Communicating with an actor. -} {- | These functions allow you to communicate with an actor. -} call, cast, {- * Example. -} -- $example ) where import Control.Concurrent (Chan, newEmptyMVar, putMVar, takeMVar, writeChan) import Control.Monad.IO.Class (MonadIO, liftIO) {- $example This is an example actor that consist of a thread looping over the messages received in a 'Chan' and responding to each one. Note that 'Chan' is already an instance of 'Actor'. > import Control.Concurrent (forkIO) > import OM.Actor (Responder, respond, call) > > data Message > = AddOne Int (Responder Int) > = IntToString Int (Responder String) > > startActor :: IO (Chan Message) > startActor = do > chan <- newChan > forkIO (loop chan) > return chan > where > loop :: Chan Message -> IO void > loop chan = do > readChan >>= handleMessage > loop > > handleMessage :: Msg -> IO Responded > handleMessage (AddOne n responder) = > respond responder (n + 1) > handleMessage (IntToString n responder) = > respond responder (show n) > > main :: IO () > main = do > actor <- startActor > print =<< call actor (AddOne 41) > print =<< call actor (IntToString 42) -} {- | An opaque data structure given to the actor that allows it to respond to a message. -} newtype Responder a = Responder { unResponder :: a -> IO () } instance Show (Responder a) where show _ = "" {- | The class of types that can act as the handle for an asynchronous actor. -} class Actor a where {- | The type of messages that can be sent to the actor. -} type Msg a {- | The channel through which messages can be sent to the actor. -} actorChan :: a -> Msg a -> IO () instance Actor (Chan m) where type Msg (Chan m) = m actorChan = writeChan {- | As an actor, respond to an asynchronous message. -} respond :: (MonadIO m) => Responder a -> a -> m Responded respond responder val = do liftIO (unResponder responder val) return Responded {- | Send a message to an actor, and wait for a response. The second argument, @mkMessage@ tells 'call' how to construct a message, given a responder. You will typically want to package that responder into the message itself, so that whatever actor is processing the messages has a way to respond. For instance, you message may look like this: > data Message > = AddOne Int (Responder Int) > | IntToString Int (Responder String) Then you can use 'call' like so: > call actor (AddOne 1) :: IO Int > > call actor (IntToString 1) :: IO String -} call :: (Actor actor, MonadIO m) => actor {- ^ The actor to which to send the message. -} -> (Responder a -> Msg actor) {- ^ @mkMessage@: How to construct the message, given a responder. -} -> m a call actor mkMessage = liftIO $ do mVar <- newEmptyMVar actorChan actor (mkMessage (Responder (putMVar mVar))) takeMVar mVar {- | Send a message to an actor, but do not wait for a response. -} cast :: (Actor actor, MonadIO m) => actor -> Msg actor -> m () cast actor = liftIO . actorChan actor {- | Proof that 'respond' was called. Actor implementations can use this in a type signature when they require that 'respond' be called at least once, because calling 'respond' is the only way to generate values of this type. For actors that respond to messages, this helps the compiler ensure that a response is in fact generated for every case. -} data Responded = Responded