-- | -- Module : Control.Concurrent.Actor -- Copyright : (c) 2014 Forkk -- License : MIT -- Maintainer : forkk@forkk.net -- Stability : experimental -- Portability : GHC only (requires throwTo) -- -- This module implements Erlang-style actors (what Erlang calls processes). -- -- @ module Control.Concurrent.Actor -- * Types ( ActorHandle , ActorMessage , ActorM -- * Sending Messages , send , sendIO -- * Receiving Messages , receive , receiveMaybe -- * Spawning Actors , spawnActor , runActor -- * Getting Information , self , actorThread -- , monitor -- , link -- , kill -- , status -- , setFlag -- , clearFlag -- , toggleFlag -- , testFlag ) where import Control.Concurrent import Control.Concurrent.STM import Control.Monad.Reader -- {{{ Types -- {{{ Message -- | The `ActorMessage` class must be implemented by any type that will be sent -- as a message to actors. -- Any given type of actor will have one `ActorMessage` type that is sent to -- that actor. This ensures type safety. -- Currently this is simply a dummy class with nothing in it, but things may be -- added in the future. class ActorMessage msg -- Allow actors that don't take messages. instance ActorMessage () -- }}} -- {{{ Handle and context -- | An `ActorHandle` acts as a reference to a specific actor. data ActorMessage msg => ActorHandle msg = ActorHandle { ahContext :: ActorContext msg -- Context for this handle's actor. , ahThread :: ThreadId -- The actor's thread ID. } -- | The `ActorContext` holds shared information about a given actor. -- This is information such as the actor's mail box, the list of actors it's -- linked to, etc. data ActorMessage msg => ActorContext msg = ActorContext { acMailBox :: MailBox msg -- Channel for the actor's messages. } -- | The type for the actor's mail box. type MailBox msg = TChan msg -- }}} -- }}} -- | The base actor monad. type ActorM msg = ReaderT (ActorContext msg) IO -- {{{ Get info -- | Gets a handle to the current actor. self :: ActorMessage msg => ActorM msg (ActorHandle msg) self = do context <- ask thread <- liftIO $ myThreadId return $ ActorHandle context thread -- | Retrieves the mail box for the current actor. myMailBox :: ActorMessage msg => ActorM msg (MailBox msg) myMailBox = asks acMailBox -- }}} -- {{{ Receiving -- | Reads a message from the actor's mail box. -- If there are no messages, blocks until one is received. If you don't want -- this, use `receiveMaybe` instead. receive :: ActorMessage msg => ActorM msg (msg) receive = do chan <- myMailBox -- Read from the channel, retrying if there is nothing to read. liftIO $ atomically $ readTChan chan -- | Reads a message from the actor's mail box. -- If there are no messages, returns `Nothing`. receiveMaybe :: ActorMessage msg => ActorM msg (Maybe msg) receiveMaybe = do chan <- myMailBox liftIO $ atomically $ tryReadTChan chan -- }}} -- {{{ Sending -- | Sends a message to the given actor handle. -- This is secretly just `sendIO` lifted into an actor monad. send :: (ActorMessage msg, ActorMessage msg') => ActorHandle msg -> msg -> ActorM msg' () send hand msg = liftIO $ sendIO hand msg -- | Sends a message to the given actor handle from within the IO monad. sendIO :: ActorMessage msg => ActorHandle msg -> msg -> IO () sendIO hand msg = atomically $ writeTChan mailBox $ msg where mailBox = handleMailBox hand -- }}} -- {{{ Spawning -- | Internal function for starting actors. -- This takes an `ActorM` action, makes a channel for it, wraps it in exception -- handling stuff, and turns it into an IO monad. The function returns a tuple -- containing the actor's context and the IO action to execute the actor. wrapActor :: ActorMessage msg => ActorM msg () -> IO (IO (), ActorContext msg) wrapActor actorAction = do -- TODO: Exception handling. -- First, create a channel for the actor. chan <- atomically newTChan -- Next, create the context and run the ReaderT action. let context = ActorContext chan ioAction = runReaderT actorAction context -- Return the information. return (ioAction, context) -- | Spawns the given actor on another thread and returns a handle to it. spawnActor :: ActorMessage msg => ActorM msg () -> IO (ActorHandle msg) spawnActor actorAction = do -- Wrap the actor action. (ioAction, context) <- wrapActor actorAction -- Fork the actor's IO action to another thread. thread <- forkIO ioAction -- Return the handle. return $ ActorHandle context thread -- | Runs the given actor on the current thread. -- This function effectively turns the current thread into the actor's thread. -- Obviously, this means that this function will block until the actor exits. -- You probably want to use this for your "main" actor. runActor :: ActorMessage msg => ActorM msg () -> IO () runActor actorAction = do -- Wrap the actor action. We discard the context, because we won't be -- returning a handle to this actor. (ioAction, _) <- wrapActor actorAction -- Execute the IO action on the current thread. ioAction -- }}} -- {{{ Utility functions -- | Gets the mail box for the given handle. handleMailBox :: ActorMessage msg => ActorHandle msg -> MailBox msg handleMailBox = acMailBox . ahContext -- | Gets the thread ID for the given actor handle. actorThread :: ActorMessage msg => ActorHandle msg -> ThreadId actorThread = ahThread -- }}}