~pepe/shawn

Control the flow
Remove death, GP you know
Merge branch 'master' of git.sr.ht:~pepe/shawn
Redesign on-error workings

clone

read-only
https://git.sr.ht/~pepe/shawn
read/write
git@git.sr.ht:~pepe/shawn

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

#Shawn

#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.

#Usage

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.

#Structure

Shawn consists of the three encapsulated parts:

  • Envelope - table with the state. The most important and user facing part.
  • Tide - stream of the Act confirmed or generated by the watch method.
  • Flow - 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.

#Confirming

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.

#Act

The Act is Janet object with three methods:

  • 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.

#Snooping

You can observe changes to the envelope by adding your custom function, called spy to the Shawn. Spy must be a function with an arity of two, that takes two arguments:

  • old-envelope the envelope before the update method call
  • new-envelope the envelope after the update method call

Both arguments are mutable, but it is considered antipattern to mutate them in the spy function.

#Cocoons

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 act loop independetly from the stream processing, which are dispatching acts to the shawn.

When you stop confirming, you can still wait 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.

#See more

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

#Tests

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

#Examples

#REPL

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
velope:counter)))})
@{: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.

#TODOs

  • [ ] add on-error handler
  • [o] 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