First, I definitely recommend using your own data type to represent commands. When using (String, Maybe (Chan [a]), Maybe a) an improper client can crash your actor simply by sending an unknown command or sending ("add", Nothing, Nothing) , etc. I would suggest something like
data Command a = Add a | Remove a | Get (Chan [a])
Then you can match pattern matching in storage commands in economy mode.
Actors have their advantages, but I also feel that they have some disadvantages. For example, receiving a response from an actor requires sending him a command, and then waiting for a response. And the client cannot be completely sure that he will receive the answer and that the answer will be of a certain type - you cannot say that I only need answers of this type (and how many of them) for this particular team.
So, as an example, I will give a simple STM solution. It would be better to use a hash table or set (balanced tree), but since Handle does not implement either Ord or Hashable , we cannot use these data structures, so I will continue to use lists.
module ThreadSet ( TSet, add, remove, get ) where import Control.Monad import Control.Monad.STM import Control.Concurrent.STM.TVar import Data.List (delete) newtype TSet a = TSet (TVar [a]) add :: (Eq a) => a -> TSet a -> STM () add x (TSet v) = readTVar v >>= writeTVar v . (x :) remove :: (Eq a) => a -> TSet a -> STM () remove x (TSet v) = readTVar v >>= writeTVar v . delete x get :: (Eq a) => TSet a -> STM [a] get (TSet v) = readTVar v
This module implements a set of arbitrary elements based on STM . You can have several such sets and use them together in the same STM transaction, which immediately failed or worked. for instance
-- | Ensures that there is exactly one element `x` in the set. add1 :: (Eq a) => a -> TSet a -> STM () add1 xv = remove xv >> add xv
It would be difficult with the actors, you would need to add it as another team for the actor, you cannot compose it from existing actions and still have atomicity.
Update: There is an interesting article explaining why Clojure designers decided not to use actors. For example, using participants, even if you have a lot of readings and only very little is written in a mutable structure, they are all serialized, which can greatly affect performance.