Control the flow
2da32430 — Josef Pospíšil 5 months ago
Tune docs
6e7cee56 — Josef Pospíšil 7 months ago
Confirm returns shawn
1cd21138 — Josef Pospíšil 1 year, 1 month ago
Tune code design and docs



You can also use your local clone with git send-email.


#Reactive streams library

The goal is to have something like funcool/potok but without all the RX cruft. That was the orginal plan anyway. But through the development the ev functionality was added to the Janet and so right now Shawn is more wrapper and model for the simple ev abstraction.


Shawn is the basic building block, which contains all the data needed for the running program's domain.

The only changes that you can do to Shawn are through confirming the Act.

You can attach Cocoons to the Shawn with long running processes, which do not fit into stream processing.


Shawn consists of the three encapsulated parts:

  • Envelope - table with the state. The most important and user facing part.
  • Tide - stream of the Acts confirmed or generated by the watch method.
  • Flow - on demand created ev channel to supervise the Cocoons.

For most of the time, your concern is only the Envelope.

#Initialising the Shawn

The Shawn is any table with the prototype of Shawn. The first thing to start with Shawn is to initialize it. For this purpose there is [inititiaze]. The function takes two arguments:

  • envelope which must be a table and is the initial value of the state.
  • on-error which must be a function and is used as the error handler when calling one of the Act's methods. The function returns the new initialized Shawn. Envelope is the datastructure, where Shawn's state is held.


The main mechanism of interaction with the Shawn is confirming. For this purpose there is [confirm]. This function takes a variable amount of the acts which you want to confirm.


The Act is Janet object with four methods:

  • :spy - this method should return Snoop of array of them. Snoops are run after every :update method of every act.
  • :update - mechanism to change anything in the Envelope. The function takes an Act and the Envelope as arguments. It mutates the Envelope.
  • :watch - mechanism to add new Acts to Tide. Watchable are Act, Array of Acts or fiber. The function takes an Act, Envelope, and Tide as its arguments. It is antipattern to mutate envelope in the watch method.
  • :effect - mechanism to perform the side effects. The function takes an Act, Envelope, and Tide as its arguments. It is antipatter to mutate any of them.

There are two utility methods for creating acts:

  • make function, which takes a table and sets its prototype to Act.
  • defact macro, which defines the new act through make under supplied name.

There is also the valid? function, which checks if the act is valid.


Snoop is similar to the Act, but it is not removed after one call. Its :snoop method is called after every :update method of every Act. This method call should return Acts, which are added to the Tide as is done for the call to :watch method.

This is main mechanism for the reactivity.


The functionality described above is good for reactive stuff. First use case is where there is inital stack of acts and you model their flow. Second use case is for programs which reacts to some external inputs like user input.

Cocoons solves the situation, where you have code which runs on the event loop independetly from the stream processing, which are dispatching acts to the shawn.

When you stop confirming, you can still wait, by calling :admit method of the Shawn, for this code to run and get its results or even confirm acts it produces.

Cocoons are advanced topics, not needed by everyone, so they are described in bigger detail in TECH.md.

#Cocoon Thread


#See more

There is evolving TECH.md document with more implementation details and other usual food for thougt.


Run tests with jpm test or continuously with watch-test. For watching you need fd and entr.



If you want to try shawn in the repl:

➜ janet -l shawn -l shawn/act
Janet 1.13.2-dev-local linux/x64 - '(doc)' for help
repl:1:> (def shawn (init-shawn @{:counter 0}))
@{:envelope @{:counter 0}}
repl:2:> (defact IncreaseCounter @{:update (fn [_ envelope] (update envelope :counter inc))})
@{:update <function 0x563D27074CA0>}
repl:3:> (defact PrintCounter @{:effect (fn [_ envelope _] (print "Counter is: " (en
@{:effect <function 0x563D270761C0>}
repl:4:> (def inc-and-print (make @{:watch (fn [_ _ _] [IncreaseCounter PrintCounter])}))
@{:watch <function 0x563D27077140>}
repl:5:> (:confirm shawn inc-and-print)
Counter is: 1
@{:processing false :envelope @{:counter 1}}

#In files

There are more examples in the examples directory. You can read more about them in README.


  • [ ] more docs
  • [x] add on-error handler
  • [x] big rename
  • [x] refactor
  • [x] add combined and error act test
  • [x] write down philosophy and tech
  • [x] make fiber based only?
  • [x] add basic documentation
  • [x] add threads