packages feed

drama 0.3.0.0 → 0.4.0.0

raw patch · 12 files changed

+420/−628 lines, 12 filesdep ~kidep ~transformersdep ~unagi-chanPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: ki, transformers, unagi-chan

API changes (from Hackage documentation)

- Drama.Loop: continue :: Monad m => s -> m (Either s a)
- Drama.Loop: forever :: Applicative f => f a -> f b
- Drama.Loop: loop :: Monad m => s -> (s -> m (Either s a)) -> m a
- Drama.Loop: stop :: Monad m => a -> m (Either s a)
- Drama.Process: data Address msg
- Drama.Process: data NoMsg
- Drama.Process: data Process msg a
- Drama.Process: here :: HasMsg msg => Process msg (Address msg)
- Drama.Process: liftIO :: MonadIO m => IO a -> m a
- Drama.Process: receive :: HasMsg msg => Process msg msg
- Drama.Process: run :: (HasMsg msg, MonadIO m) => Process msg a -> m a
- Drama.Process: run_ :: MonadIO m => Process NoMsg a -> m a
- Drama.Process: send :: HasMsg msg => Address msg -> msg -> Process _msg ()
- Drama.Process: spawn :: HasMsg msg => Process msg () -> Process _msg (Address msg)
- Drama.Process: spawn_ :: Process NoMsg () -> Process msg ()
- Drama.Process: tryReceive :: HasMsg msg => Process msg (Maybe msg)
- Drama.Process: type family HasMsg msg :: Constraint
- Drama.Process: wait :: Process msg ()
- Drama.Process.Internal: Address :: InChan msg -> Address msg
- Drama.Process.Internal: Mailbox :: OutChan msg -> Mailbox msg
- Drama.Process.Internal: Process :: ReaderT (ProcessEnv msg) IO a -> Process msg a
- Drama.Process.Internal: ProcessEnv :: Address msg -> Mailbox msg -> Scope -> ProcessEnv msg
- Drama.Process.Internal: Scope :: Scope -> Scope
- Drama.Process.Internal: [address] :: ProcessEnv msg -> Address msg
- Drama.Process.Internal: [mailbox] :: ProcessEnv msg -> Mailbox msg
- Drama.Process.Internal: [scope] :: ProcessEnv msg -> Scope
- Drama.Process.Internal: data NoMsg
- Drama.Process.Internal: data ProcessEnv msg
- Drama.Process.Internal: here :: HasMsg msg => Process msg (Address msg)
- Drama.Process.Internal: instance Control.Monad.Fail.MonadFail (Drama.Process.Internal.Process msg)
- Drama.Process.Internal: instance Control.Monad.Fix.MonadFix (Drama.Process.Internal.Process msg)
- Drama.Process.Internal: instance Control.Monad.IO.Class.MonadIO (Drama.Process.Internal.Process msg)
- Drama.Process.Internal: instance GHC.Base.Alternative (Drama.Process.Internal.Process msg)
- Drama.Process.Internal: instance GHC.Base.Applicative (Drama.Process.Internal.Process msg)
- Drama.Process.Internal: instance GHC.Base.Functor (Drama.Process.Internal.Process msg)
- Drama.Process.Internal: instance GHC.Base.Monad (Drama.Process.Internal.Process msg)
- Drama.Process.Internal: instance GHC.Base.MonadPlus (Drama.Process.Internal.Process msg)
- Drama.Process.Internal: newtype Address msg
- Drama.Process.Internal: newtype Mailbox msg
- Drama.Process.Internal: newtype Process msg a
- Drama.Process.Internal: newtype Scope
- Drama.Process.Internal: receive :: HasMsg msg => Process msg msg
- Drama.Process.Internal: run :: (HasMsg msg, MonadIO m) => Process msg a -> m a
- Drama.Process.Internal: runProcess :: MonadIO m => ProcessEnv msg -> Process msg a -> m a
- Drama.Process.Internal: run_ :: MonadIO m => Process NoMsg a -> m a
- Drama.Process.Internal: send :: HasMsg msg => Address msg -> msg -> Process _msg ()
- Drama.Process.Internal: spawn :: HasMsg msg => Process msg () -> Process _msg (Address msg)
- Drama.Process.Internal: spawn_ :: Process NoMsg () -> Process msg ()
- Drama.Process.Internal: tryReceive :: HasMsg msg => Process msg (Maybe msg)
- Drama.Process.Internal: type family HasMsg msg :: Constraint
- Drama.Process.Internal: wait :: Process msg ()
- Drama.Server: call :: HasMsg res => Address (Envelope msg) -> msg res -> Process _msg res
- Drama.Server: cast :: Address (Envelope msg) -> msg () -> Process _msg ()
- Drama.Server: data Envelope (msg :: Type -> Type)
- Drama.Server: handle :: (forall res. msg res -> Process _msg res) -> Envelope msg -> Process _msg ()
- Drama.Server: type Server msg a = Process (Envelope msg) a
- Drama.Server.Internal: [Call] :: HasMsg res => Address res -> msg res -> Envelope msg
- Drama.Server.Internal: [Cast] :: msg () -> Envelope msg
- Drama.Server.Internal: call :: HasMsg res => Address (Envelope msg) -> msg res -> Process _msg res
- Drama.Server.Internal: cast :: Address (Envelope msg) -> msg () -> Process _msg ()
- Drama.Server.Internal: data Envelope (msg :: Type -> Type)
- Drama.Server.Internal: handle :: (forall res. msg res -> Process _msg res) -> Envelope msg -> Process _msg ()
- Drama.Server.Internal: type Server msg a = Process (Envelope msg) a
+ Drama: call :: Address msg -> msg res -> Actor _msg res
+ Drama: cast :: Address msg -> msg () -> Actor _msg ()
+ Drama: data Actor (msg :: Type -> Type) a
+ Drama: data Address msg
+ Drama: data NoMsg res
+ Drama: getSelf :: Actor msg (Address msg)
+ Drama: receive :: (forall res. msg res -> Actor msg res) -> Actor msg ()
+ Drama: runActor :: MonadIO m => Actor msg a -> m a
+ Drama: runActor_ :: MonadIO m => Actor_ a -> m a
+ Drama: spawn :: Actor msg () -> Actor _msg (Address msg)
+ Drama: spawn_ :: Actor_ () -> Actor msg ()
+ Drama: tryReceive :: (forall res. msg res -> Actor msg res) -> Actor msg Bool
+ Drama: type Actor_ = Actor NoMsg
+ Drama: wait :: Actor msg ()
+ Drama.Internal: Actor :: ReaderT (ActorEnv msg) IO a -> Actor (msg :: Type -> Type) a
+ Drama.Internal: ActorEnv :: Address msg -> Mailbox msg -> Scope -> ActorEnv msg
+ Drama.Internal: Address :: InChan (Envelope msg) -> Address msg
+ Drama.Internal: Mailbox :: OutChan (Envelope msg) -> Mailbox msg
+ Drama.Internal: [Call] :: MVar res -> msg res -> Envelope msg
+ Drama.Internal: [Cast] :: msg () -> Envelope msg
+ Drama.Internal: [address] :: ActorEnv msg -> Address msg
+ Drama.Internal: [mailbox] :: ActorEnv msg -> Mailbox msg
+ Drama.Internal: [scope] :: ActorEnv msg -> Scope
+ Drama.Internal: call :: Address msg -> msg res -> Actor _msg res
+ Drama.Internal: cast :: Address msg -> msg () -> Actor _msg ()
+ Drama.Internal: data ActorEnv msg
+ Drama.Internal: data Envelope (msg :: Type -> Type)
+ Drama.Internal: data NoMsg res
+ Drama.Internal: getSelf :: Actor msg (Address msg)
+ Drama.Internal: instance Control.Monad.Fail.MonadFail (Drama.Internal.Actor msg)
+ Drama.Internal: instance Control.Monad.Fix.MonadFix (Drama.Internal.Actor msg)
+ Drama.Internal: instance Control.Monad.IO.Class.MonadIO (Drama.Internal.Actor msg)
+ Drama.Internal: instance GHC.Base.Alternative (Drama.Internal.Actor msg)
+ Drama.Internal: instance GHC.Base.Applicative (Drama.Internal.Actor msg)
+ Drama.Internal: instance GHC.Base.Functor (Drama.Internal.Actor msg)
+ Drama.Internal: instance GHC.Base.Monad (Drama.Internal.Actor msg)
+ Drama.Internal: instance GHC.Base.MonadPlus (Drama.Internal.Actor msg)
+ Drama.Internal: newtype Actor (msg :: Type -> Type) a
+ Drama.Internal: newtype Address msg
+ Drama.Internal: newtype Mailbox msg
+ Drama.Internal: receive :: (forall res. msg res -> Actor msg res) -> Actor msg ()
+ Drama.Internal: runActor :: MonadIO m => Actor msg a -> m a
+ Drama.Internal: runActor_ :: MonadIO m => Actor_ a -> m a
+ Drama.Internal: spawn :: Actor msg () -> Actor _msg (Address msg)
+ Drama.Internal: spawn_ :: Actor_ () -> Actor msg ()
+ Drama.Internal: tryReceive :: (forall res. msg res -> Actor msg res) -> Actor msg Bool
+ Drama.Internal: type Actor_ = Actor NoMsg
+ Drama.Internal: wait :: Actor msg ()

Files

CHANGELOG.md view
@@ -5,8 +5,25 @@ The format is based on [Keep a Changelog][changelog], and this project adheres to the [Haskell Package Versioning Policy][pvp]. -## [Unreleased]+## [0.4.0.0] - Unreleased +- Remove low-level `Process` API in favor of high-level `Server` API+- Rename "process" to "actor" everywhere+- Consolidate everything under `Drama` and `Drama.Internal` modules+- Remove `Drama.Loop` module+- Rename `run{,_}` to `runActor{,_}`+- Rename `here` to `getSelf`+- Make `tryReceive` indicate whether a message was received by returning a+  `Bool`+- Add `Actor_` convenience type synonym+- Allow using `call` with response type of `()`++  So that you can send synchronous/blocking messages that don't have a+  meaningful response. Useful if you need to wait until a message is handled+  before proceeding.+- Send `MVar` instead of `Unagi.Chan` in `Envelope`+- Loosened dependency version bounds+ ## [0.3.0.0] - 2021-02-23  ### Added@@ -85,7 +102,7 @@  Initial release -[Unreleased]: https://github.com/evanrelf/drama/compare/v0.3.0.0...HEAD+[0.4.0.0]: https://github.com/evanrelf/drama/compare/v0.3.0.0...HEAD [0.3.0.0]: https://github.com/evanrelf/drama/releases/tag/v0.3.0.0 [0.2.0.0]: https://github.com/evanrelf/drama/releases/tag/v0.2.0.0 [0.1.0.3]: https://github.com/evanrelf/drama/releases/tag/v0.1.0.3
drama.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.4  name:        drama-version:     0.3.0.0+version:     0.4.0.0 synopsis:    Actor library for Haskell description: Actor library for Haskell category:    Concurrency@@ -55,16 +55,12 @@   import: common, hie   hs-source-dirs: src   build-depends:-      ki           >= 0.2.0.1 && < 0.3-    , transformers >= 0.5.6.2 && < 1.0-    , unagi-chan   >= 0.4.1.3 && < 0.5+      ki           >= 0.2 && < 0.3+    , transformers >= 0.5 && < 1.0+    , unagi-chan   >= 0.4 && < 0.5   exposed-modules:       Drama-    , Drama.Loop-    , Drama.Process-    , Drama.Process.Internal-    , Drama.Server-    , Drama.Server.Internal+    , Drama.Internal   executable example-shared-resource@@ -75,8 +71,3 @@ executable example-use   import: common, executable, hie   main-is: examples/use.hs---executable example-workers-  import: common, executable, hie-  main-is: examples/workers.hs
examples/shared-resource.hs view
@@ -1,20 +1,24 @@ {-# LANGUAGE BlockArguments #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NumericUnderscores #-}  module Main (main) where  import Control.Concurrent (threadDelay)+import Control.Monad (forever)+import Control.Monad.IO.Class (liftIO) import Drama import Prelude hiding (log)   main :: IO ()-main = run_ do-  -- Spawn `logger` process, which starts waiting for requests.+main = runActor_ do+  -- Spawn `logger` actor, which starts waiting for requests.   loggerAddr <- spawn logger -  -- Spawn two noisy processes, `fizzBuzz` and `navi`, which want to constantly+  -- Spawn two noisy actors, `fizzBuzz` and `navi`, which want to constantly   -- print to the console. Instead of running `putStrLn`, they will send   -- messages to `logger`.   spawn_ (fizzBuzz loggerAddr)@@ -24,36 +28,43 @@   wait  --- | Process which encapsulates access to the console (a shared resource). By+-- | Message type for `logger`+data LogMsg res where+  LogMsg :: String -> LogMsg ()+++-- | Actor which encapsulates access to the console (a shared resource). By -- sending log messages to `logger`, instead of running `putStrLn` directly, we--- can avoid interleaving logs from processes running in parallel.-logger :: Process String ()-logger = forever do-  string <- receive-  liftIO $ putStrLn string+-- can avoid interleaving logs from actors running in parallel.+logger :: Actor LogMsg ()+logger = forever $ receive \case+  LogMsg string -> liftIO $ putStrLn string  --- | Silly example process which wants to print to the console-fizzBuzz :: Address String -> Process NoMsg ()-fizzBuzz loggerAddr = do-  let log = send loggerAddr+-- | Silly example actor which wants to print to the console+fizzBuzz :: Address LogMsg -> Actor_ ()+fizzBuzz loggerAddr = go 0+  where+    log :: String -> Actor_ ()+    log string = cast loggerAddr (LogMsg string) -  loop (0 :: Int) \n -> do-    if | n `mod` 15 == 0 -> log "FizzBuzz"-       | n `mod`  3 == 0 -> log "Fizz"-       | n `mod`  5 == 0 -> log "Buzz"-       | otherwise       -> log (show n)+    go :: Int -> Actor_ ()+    go n = do+      if | n `mod` 15 == 0 -> log "FizzBuzz"+         | n `mod`  3 == 0 -> log "Fizz"+         | n `mod`  5 == 0 -> log "Buzz"+         | otherwise       -> log (show n) -    -- Delay included for nicer (slow) output-    liftIO $ threadDelay 500_000+      -- Delay included for nicer (slow) output+      liftIO $ threadDelay 500_000 -    continue (n + 1)+      go (n + 1)  --- | Silly example process which wants to print to the console-navi :: Address String -> Process NoMsg ()+-- | Silly example actor which wants to print to the console+navi :: Address LogMsg -> Actor_ () navi loggerAddr = do-  let log = send loggerAddr+  let log string = cast loggerAddr (LogMsg string)    forever do     log "Hey, listen!"
examples/use.hs view
@@ -6,14 +6,15 @@  module Main (main) where -import Control.Monad (when)+import Control.Monad (forever, when)+import Control.Monad.IO.Class (liftIO) import Data.IORef (modifyIORef, newIORef, readIORef, writeIORef) import Drama import System.Exit (exitSuccess)   main :: IO ()-main = run_ do+main = runActor_ do   bottles <- useCounter 99    forever do@@ -39,26 +40,26 @@   GetCount :: CounterMsg Int  -counter :: Int -> Server CounterMsg ()+counter :: Int -> Actor CounterMsg () counter count0 = do   UseState{get, modify} <- useState count0 -  forever $ receive >>= handle \case+  forever $ receive \case     Increment n -> modify (+ n)     Decrement n -> modify (+ negate n)     GetCount -> get   data UseCounter = UseCounter-  { increment :: forall msg. Int -> Process msg ()-  , decrement :: forall msg. Int -> Process msg ()-  , (+=) :: forall msg. Int -> Process msg ()-  , (-=) :: forall msg. Int -> Process msg ()-  , getCount :: forall msg. Process msg Int+  { increment :: forall msg. Int -> Actor msg ()+  , decrement :: forall msg. Int -> Actor msg ()+  , (+=) :: forall msg. Int -> Actor msg ()+  , (-=) :: forall msg. Int -> Actor msg ()+  , getCount :: forall msg. Actor msg Int   }  -useCounter :: Int -> Process msg UseCounter+useCounter :: Int -> Actor msg UseCounter useCounter count0 = do   counterAddr <- spawn (counter count0) @@ -81,11 +82,11 @@   ModifyState :: (s -> s) -> StateMsg s ()  -state :: s -> Server (StateMsg s) ()+state :: s -> Actor (StateMsg s) () state s0 = do   stateIORef <- liftIO $ newIORef s0 -  forever $ receive >>= handle \case+  forever $ receive \case     GetState ->       liftIO $ readIORef stateIORef @@ -101,14 +102,14 @@   data UseState s = UseState-  { get :: forall msg. HasMsg s => Process msg s-  , gets :: forall a msg. HasMsg a => (s -> a) -> Process msg a-  , put :: forall msg. s -> Process msg ()-  , modify :: forall msg. (s -> s) -> Process msg ()+  { get :: forall msg. Actor msg s+  , gets :: forall a msg. (s -> a) -> Actor msg a+  , put :: forall msg. s -> Actor msg ()+  , modify :: forall msg. (s -> s) -> Actor msg ()   }  -useState :: s -> Process msg (UseState s)+useState :: s -> Actor msg (UseState s) useState s0 = do   stateAddr <- spawn (state s0) 
− examples/workers.hs
@@ -1,47 +0,0 @@-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE LambdaCase #-}--module Main (main) where--import Data.Function ((&))-import Drama---main :: IO ()-main = run_ do-  -- Spawn `fib` process, which starts waiting for requests.-  fibAddr <- spawn fib--  let ns = [200, 400, 600]--  -- Request fibonacci numbers from `fib`. `fib` will spawn three `fibWorker`s-  -- to do all the work in the background, and then send `main` their results-  -- once they finish.-  fs <- mapM (call fibAddr . GetFibNumber) ns--  fs & mapM_ \(n, f) ->-    liftIO $ putStrLn ("Fibonacci number " <> show n <> " is " <> show f)---data FibMsg res where-  GetFibNumber :: Int -> FibMsg (Int, Integer)----- | Process which immediately spawns and delegates to "worker" processes.-fib :: Server FibMsg ()-fib = forever do-  envelope <- receive-  spawn_ (fibWorker envelope)----- | "Worker" process responsible for doing the real, time-consuming work. Dies--- after sending its result to the return address.-fibWorker :: Envelope FibMsg -> Process NoMsg ()-fibWorker = handle \case-  GetFibNumber n -> pure (n, fibs !! n)----- | Infinite list of fibonacci numbers.-fibs :: [Integer]-fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
src/Drama.hs view
@@ -6,19 +6,63 @@ -- Maintainer: evan@evanrelf.com -- -- Actor library for Haskell+--+-- ===== __Example__+--+-- An actor which encapsulates a piece of mutable state. Its @StateMsg@ type+-- specifies which messages it accepts, which messages return a response, and+-- what type that response is.+--+-- > data StateMsg s res where+-- >   GetState :: StateMsg s s+-- >   GetsState :: (s -> a) -> StateMsg s a+-- >   PutState :: s -> StateMsg s ()+-- >   ModifyState :: (s -> s) -> StateMsg s ()+-- >+-- > state :: s -> Actor (StateMsg s) ()+-- > state s0 = do+-- >   stateIORef <- liftIO $ newIORef s0+-- >+-- >   forever $ receive \case+-- >     GetState ->+-- >       liftIO $ readIORef stateIORef+-- >+-- >     GetsState f -> do+-- >       s <- liftIO $ readIORef stateIORef+-- >       pure (f s)+-- >+-- >     PutState s ->+-- >       liftIO $ writeIORef stateIORef s+-- >+-- >     ModifyState f ->+-- >       liftIO $ modifyIORef stateIORef f  module Drama-  ( -- * Lower-level processes-    module Drama.Process+  ( Actor -    -- * Higher-level processes-  , module Drama.Server+    -- * Spawning actors+  , spawn+  , wait -    -- * Helpful utilities-  , module Drama.Loop+    -- * Sending messages+  , Address+  , cast+  , call+  , getSelf++    -- * Receiving messages+  , receive+  , tryReceive++    -- * Running your program+  , runActor++    -- * Not receiving messages+  , Actor_+  , NoMsg+  , spawn_+  , runActor_   ) where -import Drama.Loop-import Drama.Process-import Drama.Server+import Drama.Internal
+ src/Drama/Internal.hs view
@@ -0,0 +1,290 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE RankNTypes #-}++{-# OPTIONS_HADDOCK not-home #-}+{-# OPTIONS_HADDOCK prune #-}++-- |+-- Module:     Drama.Internal+-- Stability:  experimental+-- License:    BSD-3-Clause+-- Copyright:  © 2021 Evan Relf+-- Maintainer: evan@evanrelf.com++module Drama.Internal where++import Control.Applicative (Alternative)+import Control.Concurrent (MVar, newEmptyMVar, putMVar, takeMVar)+import Control.Monad (MonadPlus)+import Control.Monad.Fix (MonadFix)+import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Reader (ReaderT (..), asks)+import Data.Kind (Type)++import qualified Control.Concurrent.Chan.Unagi as Unagi+import qualified Ki++-- Support `MonadFail` on GHC 8.6.5+#if MIN_VERSION_base(4,9,0)+import Control.Monad.Fail (MonadFail)+#endif+#if MIN_VERSION_base(4,13,0)+import Prelude hiding (MonadFail)+#endif+++-- | Monad supporting actor operations.+--+-- @since 0.4.0.0+newtype Actor (msg :: Type -> Type) a = Actor (ReaderT (ActorEnv msg) IO a)+  deriving newtype+    ( Functor+    , Applicative+    , Monad+    , MonadIO+    , Alternative+    , MonadPlus+#if MIN_VERSION_base(4,9,0)+    , MonadFail+#endif+    , MonadFix+    )+++-- | Ambient context provided by the `Actor` monad.+--+-- Values in `ActorEnv` are scoped to the current actor and cannot be safely+-- shared. Functions like `spawn`, `receive`, and `getSelf` use these values as+-- implicit parameters to avoid leaking internals (and for convenience).+--+-- @since 0.4.0.0+data ActorEnv msg = ActorEnv+  { address :: Address msg+    -- ^ Current actor's address.+  , mailbox :: Mailbox msg+    -- ^ Current actor's mailbox.+  , scope :: Ki.Scope+    -- ^ Current actor's token used for spawning threads. Delimits the lifetime+    -- of child actors (threads).+  }+++-- | Address for sending messages to an actor. Obtained by running `spawn`,+-- `getSelf`, or `receive` (if another actor sends you an address).+--+-- @since 0.4.0.0+newtype Address msg = Address (Unagi.InChan (Envelope msg))+++-- | Mailbox where an actor receives messages. Cannot be shared with other+-- actors; used implicitly by `receive` and `tryReceive`.+--+-- @since 0.4.0.0+newtype Mailbox msg = Mailbox (Unagi.OutChan (Envelope msg))+++-- | Wrapper around higher-kinded message types.+--+-- Higher-kinded message types are defined as GADTs with a type parameter. This+-- allows specifying the response type for messages.+--+-- @since 0.4.0.0+data Envelope (msg :: Type -> Type) where+  Cast :: msg () -> Envelope msg+  Call :: MVar res -> msg res -> Envelope msg+++-- | Message type used by actors which do not receive messages.+--+-- @since 0.4.0.0+data NoMsg res+++-- | @since 0.4.0.0+type Actor_ = Actor NoMsg+++-- | Spawn a child actor and return its address.+--+-- @since 0.4.0.0+spawn+  :: Actor msg ()+  -- ^ Actor to spawn+  -> Actor _msg (Address msg)+  -- ^ Spawned actor's address+spawn actor = do+  (inChan, outChan) <- liftIO Unagi.newChan+  let address = Address inChan+  let mailbox = Mailbox outChan+  spawnImpl address mailbox actor+  pure address+++-- | More efficient version of `spawn`, for actors which receive no messages+-- (@msg ~ `NoMsg`@). See docs for `spawn` for more information.+--+-- @since 0.4.0.0+spawn_ :: Actor_ () -> Actor msg ()+spawn_ actor = do+  let address = Address (error noMsgError)+  let mailbox = Mailbox (error noMsgError)+  spawnImpl address mailbox actor+++spawnImpl+  :: Address msg+  -> Mailbox msg+  -> Actor msg ()+  -> Actor _msg ()+spawnImpl address mailbox actor = do+  scope <- Actor $ asks scope+  liftIO $ Ki.fork_ scope $ runActorImpl address mailbox actor+++-- | Block until all child actors have terminated.+--+-- @since 0.4.0.0+wait :: Actor msg ()+wait = do+  scope <- Actor $ asks scope+  liftIO $ Ki.wait scope+++-- | Return the current actor's address.+--+-- @since 0.4.0.0+getSelf :: Actor msg (Address msg)+getSelf = Actor $ asks address+++-- | Send a message to another actor, expecting no response. Returns immediately+-- without blocking.+--+-- @since 0.4.0.0+cast+  :: Address msg+  -- ^ Actor's address+  -> msg ()+  -- ^ Message to send+  -> Actor _msg ()+cast (Address inChan) msg = liftIO $ Unagi.writeChan inChan (Cast msg)+++-- | Send a message to another actor, and wait for a response.+--+-- @since 0.4.0.0+call+  :: Address msg+  -- ^ Actor's address+  -> msg res+  -- ^ Message to send+  -> Actor _msg res+  -- ^ Response+call (Address inChan) msg = liftIO do+  resMVar <- newEmptyMVar+  Unagi.writeChan inChan (Call resMVar msg)+  takeMVar resMVar+++-- | Receive a message. When the mailbox is empty, blocks until a message+-- arrives.+--+-- @since 0.4.0.0+receive+  :: (forall res. msg res -> Actor msg res)+  -- ^ Callback function that responds to messages+  -> Actor msg ()+receive callback = do+  Mailbox outChan <- Actor $ asks mailbox+  envelope <- liftIO $ Unagi.readChan outChan+  case envelope of+    Cast msg ->+      callback msg+    Call resMVar msg -> do+      res <- callback msg+      liftIO $ putMVar resMVar res+++-- | Try to receive a message. When the mailbox is empty, returns immediately.+--+-- @since 0.4.0.0+tryReceive+  :: (forall res. msg res -> Actor msg res)+  -- ^ Callback function that responds to messages+  -> Actor msg Bool+tryReceive callback = do+  Mailbox outChan <- Actor $ asks mailbox+  (element, _) <- liftIO $ Unagi.tryReadChan outChan+  envelope <- liftIO $ Unagi.tryRead element+  case envelope of+    Nothing ->+      pure False+    Just (Cast msg) -> do+      callback msg+      pure True+    Just (Call resMVar msg) -> do+      res <- callback msg+      liftIO $ putMVar resMVar res+      pure True+++-- | Run a top-level actor. Intended to be used at the entry point of your+-- program.+--+-- If your program is designed with actors in mind, you can use `Actor` as+-- your program's base monad:+--+-- > main :: IO ()+-- > main = runActor root+-- >+-- > root :: Actor RootMsg ()+-- > root = do+-- >   ...+--+-- Otherwise, use `runActor` like you would with @run@ functions from libraries+-- like @transformers@ or @mtl@.+--+-- @since 0.4.0.0+runActor :: MonadIO m => Actor msg a -> m a+runActor actor = do+  (inChan, outChan) <- liftIO Unagi.newChan+  let address = Address inChan+  let mailbox = Mailbox outChan+  runActorImpl address mailbox actor+++-- | More efficient version of `runActor`, for actors which receive no messages+-- (@msg ~ `NoMsg`@). See docs for `runActor` for more information.+--+-- @since 0.4.0.0+runActor_ :: MonadIO m => Actor_ a -> m a+runActor_ actor = do+  let address = Address (error noMsgError)+  let mailbox = Mailbox (error noMsgError)+  runActorImpl address mailbox actor+++runActorImpl :: MonadIO m => Address msg -> Mailbox msg -> Actor msg a -> m a+runActorImpl address mailbox (Actor reader) =+  liftIO $ Ki.scoped \scope ->+    runReaderT reader ActorEnv{address, mailbox, scope}+++noMsgError :: String+noMsgError = unlines . fmap unwords $+  [ ["[!] drama internal error"]+  , []+  , [ "Attempted to use the address or mailbox of a actor which cannot send"+    , "or receive messages (msg ~ NoMsg)."+    ]+  , [ "This should be impossible using non-internal modules!" ]+  , []+  , [ "Please report this issue at https://github.com/evanrelf/drama/issues"+    ]+  ]
− src/Drama/Loop.hs
@@ -1,65 +0,0 @@-{-# LANGUAGE LambdaCase #-}---- |--- Module:     Drama.Loop--- Stability:  experimental--- License:    BSD-3-Clause--- Copyright:  © 2021 Evan Relf--- Maintainer: evan@evanrelf.com--module Drama.Loop-  ( loop-  , continue-  , stop--    -- * Re-exports-  , forever-  )-where--import Control.Monad (forever)----- | Loop indefinitely with state. Use `Control.Monad.forever` for stateless--- infinite loops.------ ===== __ Example __------ > counter :: Process NoMsg ()--- > counter = loop (10 :: Int) \count -> do--- >   liftIO $ print count--- >   if count > 0--- >     then continue (count - 1)--- >     else exit ()------ @since 0.3.0.0-loop-  :: Monad m-  => s-  -- ^ Initial state-  -> (s -> m (Either s a))-  -- ^ Action to perform, returning either a new state to continue looping, or-  -- a final value to stop looping.-  -> m a-loop s0 k =-  k s0 >>= \case-    Left s -> loop s k-    Right x -> pure x----- | Continue looping with some new state.------ prop> continue s = pure (Left s)------ @since 0.3.0.0-continue :: Monad m => s -> m (Either s a)-continue s = pure (Left s)----- | Stop looping and return with a final value.------ prop> exit x = pure (Right x)------ @since 0.3.0.0-stop :: Monad m => a -> m (Either s a)-stop x = pure (Right x)
− src/Drama/Process.hs
@@ -1,42 +0,0 @@--- |--- Module:     Drama.Process--- Stability:  experimental--- License:    BSD-3-Clause--- Copyright:  © 2021 Evan Relf--- Maintainer: evan@evanrelf.com------ Lower-level processes, supporting `spawn`, `send`, `receive` and other--- related operations. Inspired by Elixir and Erlang's processes.--module Drama.Process-  ( Process--    -- * Spawning processes-  , spawn-  , wait--    -- * Sending messages-  , Address-  , send-  , here--    -- * Receiving messages-  , receive-  , tryReceive--    -- * Running your program-  , run--    -- * Not receiving messages-  , NoMsg-  , spawn_-  , run_-  , HasMsg--    -- * Re-exports-  , liftIO-  )-where--import Control.Monad.IO.Class (liftIO)-import Drama.Process.Internal
− src/Drama/Process/Internal.hs
@@ -1,266 +0,0 @@-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-}---- For `HasMsg msg`-{-# OPTIONS_GHC -Wno-redundant-constraints #-}--{-# OPTIONS_HADDOCK not-home #-}-{-# OPTIONS_HADDOCK prune #-}---- |--- Module:     Drama.Process.Internal--- Stability:  experimental--- License:    BSD-3-Clause--- Copyright:  © 2021 Evan Relf--- Maintainer: evan@evanrelf.com--module Drama.Process.Internal where--import Control.Applicative (Alternative)-import Control.Monad (MonadPlus)-import Control.Monad.Fix (MonadFix)-import Control.Monad.IO.Class (MonadIO (..))-import Control.Monad.Trans.Reader (ReaderT (..), asks)-import Data.Kind (Constraint)-import Data.Void (Void)-import GHC.TypeLits (ErrorMessage (..), TypeError)--import qualified Control.Concurrent.Chan.Unagi as Unagi-import qualified Ki---- Support `MonadFail` on GHC 8.6.5-#if MIN_VERSION_base(4,9,0)-import Control.Monad.Fail (MonadFail)-#endif-#if MIN_VERSION_base(4,13,0)-import Prelude hiding (MonadFail)-#endif----- | Monad supporting actor operations. Inspired by Elixir and Erlang's--- processes.------ @since 0.3.0.0-newtype Process msg a = Process (ReaderT (ProcessEnv msg) IO a)-  deriving newtype-    ( Functor-    , Applicative-    , Monad-    , MonadIO-    , Alternative-    , MonadPlus-#if MIN_VERSION_base(4,9,0)-    , MonadFail-#endif-    , MonadFix-    )----- | Provided some `ProcessEnv`, convert a `Process` action into an `IO`--- action.------ @since 0.3.0.0-runProcess :: MonadIO m => ProcessEnv msg -> Process msg a -> m a-runProcess processEnv (Process reader) = liftIO $ runReaderT reader processEnv----- | Ambient context provided by the `Process` monad.------ Values in `ProcessEnv` are scoped to the current process and cannot be safely--- shared. Functions like `spawn`, `receive`, and `here` use these values as--- implicit parameters to avoid leaking internals (and for convenience).------ @since 0.3.0.0-data ProcessEnv msg = ProcessEnv-  { address :: Address msg-    -- ^ Current process' address.-  , mailbox :: Mailbox msg-    -- ^ Current process' mailbox.-  , scope :: Scope-    -- ^ Current process' token used for spawning threads.-  }----- | Address for sending messages to a process. Obtained by running `spawn`,--- `here`, or `receive` (if another process sends you an address).------ @since 0.3.0.0-newtype Address msg = Address (Unagi.InChan msg)----- | Mailbox where a process receives messages. Cannot be shared with other--- processes; used implicitly by `receive` and `tryReceive`.------ @since 0.3.0.0-newtype Mailbox msg = Mailbox (Unagi.OutChan msg)----- | Token delimiting the lifetime of child processes (threads) created by a--- process.------ @since 0.3.0.0-newtype Scope = Scope Ki.Scope----- | Constraint which prevents setting `msg ~ Void`, and provides helpful type--- errors.------ @since 0.3.0.0-type family HasMsg msg :: Constraint where-  HasMsg NoMsg = TypeError ('Text "Processes with 'msg ~ NoMsg' cannot receive messages")-  HasMsg Void = TypeError ('Text "Use 'msg ~ NoMsg' instead of 'msg ~ Void' for processes which do not receive messages")-  HasMsg () = TypeError ('Text "Use 'msg ~ NoMsg' instead of 'msg ~ ()' for processes which do not receive messages")-  HasMsg msg = ()----- | Message type used by processes which do not receive messages.------ @since 0.3.0.0-data NoMsg----- | Spawn a child process and return its address.------ @since 0.3.0.0-spawn-  :: HasMsg msg-  => Process msg ()-  -- ^ Process to spawn-  -> Process _msg (Address msg)-  -- ^ Spawned process' address-spawn process = do-  (inChan, outChan) <- liftIO Unagi.newChan-  let address = Address inChan-  let mailbox = Mailbox outChan-  spawnImpl address mailbox process-  pure address----- | More efficient version of `spawn`, for processes which receive no messages--- (@msg ~ `NoMsg`@). See docs for `spawn` for more information.------ @since 0.3.0.0-spawn_ :: Process NoMsg () -> Process msg ()-spawn_ process = do-  let address = Address (error voidMsgError)-  let mailbox = Mailbox (error voidMsgError)-  spawnImpl address mailbox process---spawnImpl-  :: Address msg-  -> Mailbox msg-  -> Process msg ()-  -> Process _msg ()-spawnImpl address mailbox process = do-  Scope kiScope <- Process $ asks scope-  liftIO $ Ki.fork_ kiScope $ runImpl address mailbox process----- | Block until all child processes have terminated.------ @since 0.3.0.0-wait :: Process msg ()-wait = do-  Scope kiScope <- Process $ asks scope-  liftIO $ Ki.wait kiScope----- | Return the current process' address.------ @since 0.3.0.0-here :: HasMsg msg => Process msg (Address msg)-here = Process $ asks address----- | Send a message to another process.------ @since 0.3.0.0-send-  :: HasMsg msg-  => Address msg-  -- ^ Other process' address-  -> msg-  -- ^ Message to send-  -> Process _msg ()-send (Address inChan) msg = liftIO $ Unagi.writeChan inChan msg----- | Receive a message. When the mailbox is empty, blocks until a message--- arrives.------ @since 0.3.0.0-receive :: HasMsg msg => Process msg msg-receive = do-  Mailbox outChan <- Process $ asks mailbox-  liftIO $ Unagi.readChan outChan----- | Try to receive a message. When the mailbox is empty, returns `Nothing`.------ @since 0.3.0.0-tryReceive :: HasMsg msg => Process msg (Maybe msg)-tryReceive = do-  Mailbox outChan <- Process $ asks mailbox-  (element, _) <- liftIO $ Unagi.tryReadChan outChan-  liftIO $ Unagi.tryRead element----- | Run a top-level process. Intended to be used at the entry point of your--- program.------ If your program is designed with processes in mind, you can use `Process` as--- your program's base monad:------ > main :: IO ()--- > main = run do--- >   ...------ Otherwise, use `run` like you would with @run@ functions from libraries like--- @transformers@ or @mtl@.------ @since 0.3.0.0-run :: (HasMsg msg, MonadIO m) => Process msg a -> m a-run process = do-  (inChan, outChan) <- liftIO Unagi.newChan-  let address = Address inChan-  let mailbox = Mailbox outChan-  runImpl address mailbox process----- | More efficient version of `run`, for processes which receive no messages--- (@msg ~ `NoMsg`@). See docs for `run` for more information.------ @since 0.3.0.0-run_ :: MonadIO m => Process NoMsg a -> m a-run_ process = do-  let address = Address (error voidMsgError)-  let mailbox = Mailbox (error voidMsgError)-  runImpl address mailbox process---runImpl :: MonadIO m => Address msg -> Mailbox msg -> Process msg a -> m a-runImpl address mailbox process = do-  liftIO $ Ki.scoped \kiScope -> do-    let scope = Scope kiScope-    runProcess ProcessEnv{address, mailbox, scope} process---voidMsgError :: String-voidMsgError = unlines . fmap unwords $-  [ ["[!] drama internal error"]-  , []-  , [ "Attempted to use the address or mailbox of a process which cannot send"-    , "or receive messages (msg ~ NoMsg)."-    ]-  , [ "This should be impossible using non-internal modules!" ]-  , []-  , [ "Please report this issue at https://github.com/evanrelf/drama/issues"-    ]-  ]
− src/Drama/Server.hs
@@ -1,54 +0,0 @@--- |--- Module:     Drama.Server--- Stability:  experimental--- License:    BSD-3-Clause--- Copyright:  © 2021 Evan Relf--- Maintainer: evan@evanrelf.com------ Higher-level process operations, allowing responses to messages. Inspired by--- Elixir and Erlang's generic servers (@GenServer@ / @gen_server@).------ ===== __Example__------ A server which encapsulates a piece of mutable state. Its @StateMsg@ type--- specifies which messages it accepts, which messages return a response, and--- what type that response is.------ > data StateMsg s res where--- >   GetState :: StateMsg s s--- >   GetsState :: (s -> a) -> StateMsg s a--- >   PutState :: s -> StateMsg s ()--- >   ModifyState :: (s -> s) -> StateMsg s ()--- >--- > state :: s -> Server (StateMsg s) ()--- > state s0 = do--- >   stateIORef <- liftIO $ newIORef s0--- >--- >   forever $ receive >>= handle \case--- >     GetState ->--- >       liftIO $ readIORef stateIORef--- >--- >     GetsState f -> do--- >       s <- liftIO $ readIORef stateIORef--- >       pure (f s)--- >--- >     PutState s ->--- >       liftIO $ writeIORef stateIORef s--- >--- >     ModifyState f ->--- >       liftIO $ modifyIORef stateIORef f--module Drama.Server-  ( Server-  , Envelope--    -- * Sending messages-  , cast-  , call--    -- * Handling messages-  , handle-  )-where--import Drama.Server.Internal
− src/Drama/Server/Internal.hs
@@ -1,88 +0,0 @@-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE GADTSyntax #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE RankNTypes #-}--{-# OPTIONS_HADDOCK not-home #-}---- |--- Module:     Drama.Server.Internal--- Stability:  experimental--- License:    BSD-3-Clause--- Copyright:  © 2021 Evan Relf--- Maintainer: evan@evanrelf.com--module Drama.Server.Internal where--import Control.Monad.IO.Class (MonadIO (..))-import Data.Kind (Type)-import Drama.Process (HasMsg, Process, send)-import Drama.Process.Internal (Address (..))--import qualified Control.Concurrent.Chan.Unagi as Unagi----- | @since 0.3.0.0-type Server msg a = Process (Envelope msg) a----- | Wrapper around higher-kinded message types, to make them compatible with--- the lower-level `Process` machinery.------ Higher-kinded message types are defined as GADTs with a type parameter. This--- allows specifying the response type for messages.------ @since 0.3.0.0-data Envelope (msg :: Type -> Type) where-  Cast :: msg () -> Envelope msg-  Call :: HasMsg res => Address res -> msg res -> Envelope msg----- | Send a message to another process, expecting no response. Returns--- immediately without blocking.------ @since 0.3.0.0-cast-  :: Address (Envelope msg)-  -- ^ Process' address-  -> msg ()-  -- ^ Message to send-  -> Process _msg ()-cast addr msg = send addr (Cast msg)----- | Send a message to another process, and wait for a response.------ @since 0.3.0.0-call-  :: HasMsg res-  => Address (Envelope msg)-  -- ^ Process' address-  -> msg res-  -- ^ Message to send-  -> Process _msg res-  -- ^ Response-call addr msg = do-  (inChan, outChan) <- liftIO Unagi.newChan-  let returnAddr = Address inChan-  send addr (Call returnAddr msg)-  liftIO $ Unagi.readChan outChan----- | Handle messages which may require a response. This is the only way to--- consume an `Envelope`.------ @since 0.3.0.0-handle-  :: (forall res. msg res -> Process _msg res)-  -- ^ Callback function that responds to messages-  -> Envelope msg-  -- ^ Message to handle-  -> Process _msg ()-handle callback = \case-  Cast msg ->-    callback msg-  Call returnAddr msg -> do-    res <- callback msg-    send returnAddr res