The Transient monad propagate the effects of the appearance of events downstream. But what happens when I need to notify that something happened to a computation that is in another branch of the execution tree?
Since I don´t want to break my code with callbacks, I can do it with TVars, but this new kind of vars, called event vars (EVars) or pubish-subscribe vars have "last in-first out" semantics and can be combined like the rest of the Transient code. Morever the code continues normally after the
writeEvar statement when all the subscribed actions have been executed. There is no breaking of the flow, and contrary to STM, the execution is deterministic: All the subscribed actions are executed in a single thread with last-in-first-out preferences.
As you could see below, the siminarities and differences with STM does not end there.
This is an example snippet which uses an EVar:
main= keep $ do option "pubs" "an example of publish-subscribe using Event Vars (EVars)" v <- newEVar :: TransIO (EVar String) susbcribe v <|> publish v where publish :: String -> TransIO () publish v= do liftIO $ putStrLn "Enter a message to publish" msg <- input(const True) writeEVar v msg liftIO $ putStrLn "after writing the EVar" susbcribe :: EVar String -> TransIO () susbcribe v= proc1 v <|> (proc2 v) proc1 :: EVar String -> TransIO () proc1 v= do msg <- readEVar v liftIO $ putStrLn $ "proc1 readed var: " ++ show msg proc2 :: EVar String -> TransIO () proc2 v= do msg <- readEVar v liftIO $ putStrLn $ "proc2 readed var: " ++ show msg
The output is
Enter a message to publish > aaaaa proc2 readed var: "aaaaa" proc1 readed var: "aaaaa" after writing the EVar
publishwaits for a text input (
option) and then it updates the EVar. Inmediately
proc2 and then
proc1 are executed since they invoked
readEVar of this variable.
Each time that
writeEVaris executed, the process is reproduced again. It is not necessary to invoke
On the contrary, if your code invoke
readEVar multiple times, since each continuation is potentially different, the effect is that a new reference to the continuation will be added, so it will be called multiple times.
To avoid this effect, call
unsuscribe var after
readEVar var to avoid such duplication.
unsuscribe removes the current continuation from the list.
Moreover it is possible to combine two or more EVars:
do (x,y) <- (,) <$> readEVar v1 <*> readEVar v2 if x > y then stop else do .....
As you can see, EVars, like TVars, do not block. That is why both are composable.
This look like STM but it is not: when the first event is produced, it waits for the second, while in STM there is no waiting, since a STM var (TVar) ever is filled with a value.
There is no transaction here, but I guess that it is possible to implement the TVar semantics with EVars.
stop is a Transient primitive that interrupts the execution.
The next update of the variables will trigger the code again.
In the last example, the order of the events is indiferent, but in the example below, the order is honored:
do r1 <- readEVar v1 r2 <- readEVar v2 ....
This could be useful is some cases.
The example above can be executed online. Ii is in the the executable snippet in this article . Is the last option of the menu.
The implementation of the EVars is at the git repository of transient.
It is very concise thanks to the magic of Transient continuations: basically
readEVar stores in the state the list of subscribed continuations for each variable. When the variable is written
writeEVaritself extract the list of continuations from the state and execute them before resuming to normal execution.