Nim implementation of Syndicate
Go to file
Emery Hemingway 7ab4611824 Replace hashlib with nimcrypto
I didn't trust nimcrypto because it has no releases but hashlib is
not a pure Nim library and isn't hardware portable.
2024-05-23 17:44:14 +03:00
src Replace hashlib with nimcrypto 2024-05-23 17:44:14 +03:00
tests Replace "var Turn" with "Turn" 2024-04-30 12:52:40 +02:00
.envrc Better build system 2023-06-07 18:11:54 +01:00
.gitignore WiP! Value transition 2024-01-01 20:20:44 +02:00
.gitmodules Submodules considered harmful 2022-03-16 13:00:58 -05:00 Update README 2024-04-08 14:50:36 +01:00
Tupfile Depend on cps and nim-sys 2024-03-01 13:57:48 +00:00
Tuprules.tup Solo5 support 2024-04-02 16:34:33 +01:00
UNLICENSE Unlicense 2021-09-01 13:47:21 +02:00
depends.tup Add lockfile 2023-10-13 23:37:31 +01:00
lock.json Replace hashlib with nimcrypto 2024-05-23 17:44:14 +03:00
nlnet.svg Update README 2024-04-08 14:50:36 +01:00
shell.nix Solo5 support 2024-04-02 16:34:33 +01:00
syndicate.nimble Replace hashlib with nimcrypto 2024-05-23 17:44:14 +03:00

Syndicated Actors for Nim

The Syndicated Actor model is a new model of concurrency, closely related to the actor, tuplespace, and publish/subscribe models.

The Syndicate library provides a code-generator and DSL for writing idiomatic Nim within the excution flow of the Syndicated Actor Model. The library allows for structured conversations with other programs and other languages. It can be used to implement distributed systems but also makes it possible to create dynamically reconfigurable programs using a process controller and a configuration conversation.

The Preserves Data Language

Preserves is a data model and serialization format that is used to represent data in Syndicate conversations. The Preserves model features the familar boolean, integer, float, string, sequence, dictionaries, and set types. Less familar to Nim are the symbol and record types. The symbol is a string that is elevated for use in type comparisions and the record is a labelled (usually by symbol) collection of fields.

The textual representation isn't necessary to study before using Preserves because the Preserves model is a subset of Nim types and a code-generator is available to convert Preserves schemas to Nim modules.

Preserves schema

Here is an example schema for a chat protocol:

; Present is a record with symbol "Present" as record label
; and one string field referred to as "username".
Present = <Present @username string>.

; Says is a record with symbol "Says" as record label
; and two fields referred to as "who" and "what".
Says = <Says @who string @what string>.

Code Generation

The preserves_schema_nim utility would generate the following module for the preceding schema:

  Says* {.preservesRecord: "Says".} = object
    `who`*: string
    `what`*: string

  Present* {.preservesRecord: "Present".} = object
    `username`*: string

There are two types corresponding to the two records defined in the schema. The preservesRecord pragma allows for a lossless conversion between the Nim type system and Preserves records.

  present = Present(username: "Judy")
  pr = present.toPreserve()
assert $pr == """<Present "Judy">"""
assert present.fromPreserve(pr) == true

The Syndicate DSL

The Syndicate DSL can be entered using runActor which calls a Nim body with a dataspace Ref and a turn. The Ref is something that we can observe and publish assertions at, and a Turn is special type for temporal scoping and transactional semantics. Assertions can be published using the Assertion or equivalent Preserve[Ref] type, but using Nim types is preferred because they can be reliably consistent with schema.


runActor("main") do (turn: Turn):
  let dataspace = newDataspace()
  let presenceHandle = publish(turn, dataspace, Present(username: "Judy"))
    # publish <Present "Judy"> to the dataspace
    # the assertion can be later retracted by handle

  message(turn, dataspace, Says(who: "Judy", what: "greetings"))


We can react to assertions and messages within dataspaces using patterns. Patterns are constructed using a Nim type and the ? operator. Again a Nim type is used rather than a raw Preserves for schema consistency.

runActor("main") do (turn: Turn):
  let dataspace = newDataspace()
  during(turn, dataspace, ?Present) do (who: string):
    # This body is active when the ?Present pattern is matched.
    # The Present type contains two atomic values that can be matched
    # within Syndicate model, and the Nim `during` macro matches those
    # values to the types of the `do` handler.
    stderr.writeLine("<", who, " joined>")
    # This body is active when the match is retracted
    stderr.writeLine("<", who, " left>")

  onMessage(turn, dataspace, ?Says) do (who: string, what: string):
    # messages are one-shot assertions and can also be matched
    stderr.writeLine(who, ": ", what)

  onMessage(turn, dataspace, Says ? {0: grab(), 1: drop()}) do (who: string):
    # patterns can also be selectively constructed
    stderr.writeLine("<", who, " says something>")



Simple chat demo that is compatible with

SYNDICATE_ROUTE='<route [<unix "/run/user/1000/dataspace">] [<ref {oid: "syndicate" sig: #x"69ca300c1dbfa08fba692102dd82311a"}>]>' nim c -r tests/test_chat.nim --user:fnord


This work has been supported by the NLnet Foundation and the European Commission's Next Generation Internet programme. The Syndicate Actor Model through the NGI Zero PET program and this library as a part of the ERIS project through NGI Assure.