Radicle

The radicle guide

radicle is a language for building and interacting with P2P Radicle Machines. What these machines do–how they behave–is entirely up to you.

This guide provides an introduction to radicle–as a language, as a tool for building chains, and as a means of interacting with existing chains.

Please see the [installation instructions](http://radicle.xyz/docs/index.html#installation-setup) to get started.

Basics

Though radicle is designed with a particular use-case in mind–namely, defining and interacting with decentralised state machines–it is ultimately a general purpose language, quite similar to Scheme or Clojure. To effectively use it, it is helpful to learn the basics of it independent of its application.

This chapter introduces radicle as a language. In each section we present a core datatype of the language, and show some basic operations that can be performed on them.

Comments

The string ;; starts a comment. This comments all the text until the next line-break. In this guide we will often add a comment as follows:

(+ 1 1) ;; ==> 2

to indicate that an expression (in this case (+ 1 1)) evaluates to a specific value (in this case 2).

Built-in datatypes

radicle supports the following datatypes:

Booleans

The literal syntax for booleans is #t for true, and #f for false. Booleans can be combined with and and or:

(and #t #f) ;; ==> #f
(or  #t #f) ;; ==> #t
Strings

Strings are enclosed in double quotes:

"this is a string"
Keywords

Keywords are identifiers that evaluate to themselves. To create a keyword you prefix : onto an identifier (:foo is a keyword, for example). Keywords are often used as keys in maps which act like records in other languages. The colon at the front is a prefix form of the colon that follows the name of a field in other languages. For example, {name: "foo", age: 22} in JavaScript becomes {:name "foo" :age 22} in Radicle.

:foo ;; ==> :foo

Atoms

Atoms are identifiers which, when evaluated, lookup the value bound to that identifier in the current environment:

(def foo 42)

foo ;; ==> 42
Vectors

radicle’s vectors are an array-like container for values. Literal notation for vectors uses square brackets:

["this" "is" "a" "vector"]

You can access any of the elements in the vector using nth:

(nth 2 ["start at zero!" "one" "two" "three"]) ;; ==> "two"

But be careful: if the size of the argument isn’t less than (<) the size of the vector then this will result in an error.

Concatenate vectors using <>:

(<> [1 2 3] [4 5 6]) ;; ==> [1 2 3 4 5 6]

Add an element to the left using cons:

(cons 0 [1 2 3]) ;; ==> [0 1 2 3]

Add an element to the right using add-right:

(add-right 3 [0 1 2]) ;; ==> [0 1 2 3]
Lists

Lists are one of the fundamental data structures of functional programming. Just like vectors are enclosed in square brackets, lists are enclosed in parentheses.

However, as you may have noticed, parentheses are also used for function application. This is because lists are the representation for code that evaluates to the result of applying the first element of the list to the other elements.

Don’t worry if this is still a little unclear; we will discuss it at greater length when we talk about evaluation and quotation. For now, what this in practice means is that if you want to create a list, you can use the function list on the elements you want in the list. You can also use list-from-vec to make a vector into a list.

You can prepend an element to a list with cons:

(cons 0 (list 1)) ;; ==> (0 1)

Retrieve the head of a list with head:

(head (list 0 1 2 3)) ;; ==> 0

The rest of it with tail:

(tail (list 0 1 2 3)) ;; ==> (1 2 3)

Or a specific index with nth:

(nth 2 (list 0 1 2 3)) ;; ==> 2
Lists or vectors?

Vectors differ from lists in a couple of important ways. First, lists are implemented as linked lists, and accessing the nth element of a list has linear time complexity on n (the function nth works for both vectors and lists). Additionally, appending to the end of a list is also linear. Vectors, on the other hand, allow for faster (logarithmic) indexed access and appending (constant time).

Dictionaries

Dictionaries are mappings between keys and values. (You may know them as maps, associative arrays, or symbol tables.) Literal syntax for dictionaries is formed by including the keys and values inside curly braces:

{ :key1 1
  :key2 "foo"
}

It is an error to have an odd number of elements inside the braces.

Keys may be any value, including other dictionaries:

{ :key1 1
  { :nested "foo" } "bar"
}

You can access the value associated with a key with the function lookup:

(lookup :key1 { :key1 1 }) ;; ==> 1

And add new values with insert:

(insert :key2 2 { :key1 1 }) ;; ==> { :key1 1 :key2 2 }

Simple expressions

Branching

To dispatch on a boolean value use if:

(def num-bugs 50)

(if (> num-bugs 0)
  "Oh no, so many bugs!"
  "All done!")

To test multiple booleans use cond:

(cond
  (> num-bugs 100) "Oh no, so many bugs!"
  (eq? num-bugs 0)   "All done!"
  #t               "A few bugs.")

In fact all values which are not #f are treated as true, so one can also write:

(cond
  (> num-bugs 100) "Oh no, so many bugs!"
  (eq? num-bugs 0)   "All done!"
  :else              "A few bugs.")

If none of the conditions in the cond are true then the result is an error!

Function application

A function call takes prefixed, parenthesized form. That is, the call appears within parenthesis, with the function at the head, followed by its arguments:

(+ 3 2) ;; => 5
Definitions

Definitions bind values to an identifier.

(def five 5)
five ;; ==> 5
Functions

A function is created using fn. The first argument is a vector of symbols representing the parameters of the function, and the rest is a sequence of expressions which are run when the function is called.

So a function which adds one to a number can be defined like so:

(fn [x] (+ x 1))

You can call it like any other function:

((fn [x] (+ x 1)) 1) ;; ==> 2

But most likely you want to define a new variable:

(def f (fn [x] (+ x 1)))

(f 41) ;; ==> 42

Functions are not by default recursive. If you want to define a recursive function, use the def-rec form:

(def-rec range
 (fn [from to]
  (if (<= to from)
    (list)
    (cons from (range (+ 1 from) to)))))

Note that def-rec may only be used to define functions.

For creating one-off anonymous functions quickly, there is a short-form. This is formed by a \ character (meant to look like the main stroke of a λ) and then a valid radicle expression, which will be the return value of the function. To make a single parameter function use ? for the argument. To make a multi-parameter function, use ?1, ?2, etc. Here are some examples of short-form lambdas and the long-form equivalent:

;; The identity function.
\?
(fn [x] x)

;; Partial application:
\(foo 0 ?)
(fn [x] (foo 0 x))

;; A function to make singleton vectors:
\[?]
(fn [x] [x])

;; Multiple arguments:
\(string-append ?1 " is the parent of " ?2)
(fn [x y] (string-append x " is the parent of " y))

;; Thunks:
\(put-str! "hello there!")
(fn [] (put-str! "hello there"))

Note that:

  • Only ?1, ?2, …, ?9 are allowed for positional arguments. If you want more that 9 parameters please seek professional help.
  • Short-form lambdas cannot be nested.
  • You cannot use a mixture of ? and ?1, ?2, etc. inside a short-form lambda.
  • To avoid hard to detect bugs, atoms starting with a ? followed by a sequence of digits that isn’t one of the allowed positional atoms will cause an error.

Quote and eval

Modules

To create a module, use the module special form. All modules must start with a module declaration which provides the module with a name, documentation string and an exports vector. Module declarations are evaluated, so you must quote any exports.

Here is a simple example of a module:

(module
  {:module  'my-module
   :doc     "Just for showing off modules."
   :exports '[foo bar]}

  (def foo-helper
    (fn [x] (+ x 1)))

  (def foo
    (fn [x] (foo-helper (foo-helper x))))

  (def bar
    (fn [x] (foo (+ x 2)))))

To import a module, use import:

(import my-module)

The content of a module is evaluated in a new scope that is identical to the scope of the module definition site. This means you you can use whatever has already been defined before the module was, and that definitions in the module don’t leak outside the module. The form returns the module value, which can be passed around just like any other value. This means that we can write functions which create modules:

(def make-taxes
  (fn [VAT]
    (module
     {:module 'taxes
      :doc    "Calculates taxes."
      :exports ['add-VAT]}
     (def add-VAT (fn [x] (* (+ 1 VAT) x))))))

And then we could use this module with different values of VAT:

(import (make-taxes 2/10))
;; We also need to import `prelude/io` to be able to use `print!`
(import (file-module! "prelude/io.rad") :unqualified)

(print! (string-append "100 EUR + VAT is: " (show (taxes/add-VAT 100))))

By default import will add all the definitions of the module in fully-qualified form, which means my-module/foo and my-module/bar (but not my-module/foo-helper) are in scope:

(print! (my-module/bar 0))

You can narrow down exactly which definitions you would like to import by including a list of atoms:

(import my-module ['foo])

Which would only import my-module/foo. And you can also use a custom qualifier:

(import my-module :as 'baz)

After which we can:

(print! (baz/bar 42))

So we could import our taxes module twive using different qualifiers:

(def taxes-fr (make-taxes 20/100))
(def taxes-de (make-taxes 19/100))

(import taxes-de :as 'de)
(import taxes-fr :as 'fr)

(print!
 (string-append
  "100 EUR + VAT is "
  (show (de/add-VAT 100))
  " EUR in Germany and "
  (show (fr/add-VAT 100))
  " EUR in France."))

If you really want to import all the definitions without a qualification, then you can use the keyword :unqualified like so:

(import my-module :unqualified)

And then we can:

(print! (foo 128))

Qualification and import lists can be combined like so:

(import my-module ['bar] :as 'useful)

Would only add useful/bar to the current scope:

(print! (useful/bar 0))

When working at the REPL, the function file-module! is also available, which can create a module from a file. It assumes the file starts with a module declaration, and is then equivalent to wrapping the contents of the file in (module ...).

Modules can be passed around and manipulated because they are just dicts with some metadata, and an :env key containing a radicle environment.

Pattern matching

Basics

Radicle has first class patterns, which means you can use match expressions to bind values according to the shape of some value, and create new patterns to be used in match expressions.

Pattern matching is invoked as follows:

;; Just getting an import out of the way
(import (file-module! "prelude/io.rad") :unqualified)

(match 42
  'x (print! (string-append "x was: " (show x))))

That’s not very interesting, but it’s all we can do before we import some more patterns:

(import (file-module! "prelude/test.rad") :unqualified)
(file-module! "prelude/basic.rad")
(import (file-module! "prelude/patterns.rad") :unqualified)

Now we can pattern match on vectors, numbers, dicts, etc. For example, evaluating:

(print!
  (match [42 [:a 3] {:key "val" :key2 "don't care"}]
    [43 [:a _] {:key 'v}]  :not-this
    [42 "hello" {:key 'v}] :not-this
    [42 [:a 'x] {:key 'v}] [:yes x v]
    _                      :not-this))

will print [:yes 3 "val"].

A valid match expression has the shape (match v ...patterns...) where ...patterns... is an even number of expressions. The patterns are the expressions at the even indices; the expressions at the odd indices are the branches. When evaluated, the value v is matched against the patterns, one at a time. For the first one that matches, the corresponding branch is then evaluated, and the result of that is the result of the expression. If none of the patterns matches v, then an exception is thrown.

Pre-defined patterns

Patterns are expressions which describe shapes that values can have, and specify atoms to bind certain parts of the value to variables.

These are the patterns that come included in the prelude:

  • _ is the wildcard pattern. It will match any value. It’s mostly useful for ignoring sub-parts of structures, or as a catch all for the last pattern.
  • An atom will match against any value too, but it will then be bound to the matched value in the corresponding branch.
  • Numbers, keywords and strings match against themselves by equality. E.g. 42 as a pattern will only match the value 42.
  • A vector of patterns [p_0 ... p_n] will match against vectors [v_0 ... v_n] of the same size, as long as p_i matches v_i.
  • A dict {k_0 p_0 ... k_n p_n} of patterns will match against dicts {k_0 v_0 ... k_n v_n ...} that have at least the keys k_0, …, k_n, and such that p_i matches v_i.
  • The pattern (/just p) will match values [:just v] as long as the pattern p matches v.
  • The pattern (/cons h t) will match against non-empty lists whose head matches h, and whose tail matches t.
  • /nil will only match the empty list.
  • The pattern (/? p), where p is a predicate function, will match all values v such that (p v) is truthy.
  • The pattern (/as x p), where x is an atom and p is a pattern, matches any values that p matches while also binding this value to x in the branch.

Binding variables

Variables used in patterns must be quoted because patterns are themselves evaluated. This means that if x is already bound to a value then it can be used in a pattern. For example:

(def x 1)

(print!
  (match [2 2]
    [x       'y] (+ y y)
    [(+ x 1) 'y] (+ x y)))

will print the number 3, that is, evaluate the second branch. This is because (+ x 1) evaluates to 2, and so this matches the first 2 of the value [2 2].

Quoting variables is also what allows custom patterns to be defined.

Non-linearity

All of the patterns included in the prelude support non-linear matching, which means re-using a variable will result in testing for equality. For example the pattern ['x 'x] will match vectors of length 2 with the same value repeated twice. And

(print!
  (match [1 2 2]
    ['x 'x 'x'] [:three x]
    ['x 'y 'y]  [:one x :and-two y]
    ['x 'y 'z]  [:all-different x y z]))

will print [:one 1 :and-two 2] because [1 2 2] matches the second pattern but not the first, as 1 is not equal to 2.

Custom patterns

You can easily create you own patterns: they are just functions with return [:just b] where b is a dict of bindings in case of a match, and :nothing when the value does not match.

For example let’s assume that we have to deal with a lot of values of the following shape:

(def alice
  {:user-id "123"
   :first-name "Alice"
   :last-name "Smith"
   :char-attributes {:class "dwarf"
                     :max-hp 132
                     :current-hp 122
                     :spell-radius 134
                     :and-lots-more "stuff"}})

(def bob
  {:user-id "345"
   :first-name "Bob"
   :last-name "Kane"
   :char-attributes {:class "elf"
                     :max-hp 96
                     :current-hp 55
                     :spell-radius 76
                     :and-lots-more "stuff"}})

And for some reason we keep having to extract the full name and spell radius if :spell-radius is over 100, but otherwise we just want the character :class and the :current-hp. We can do this with two custom patterns:

(def danger
  (fn [name spell-rad]
    (fn [v]
      (match v
        {:first-name 'fn
         :last-name  'ln
         :char-attributes {:spell-radius 'sr}}
          (if (> sr 100)
            [:just {name (string-append fn " " ln)
                    spell-rad sr}]
            :nothing)
             _ :nothing))))

(def prey
  (fn [class current-hp]
    (fn [v]
      (match v
             {:char-attributes {:class 'c
                                :current-hp 'hp}}
             [:just {class c
                     current-hp hp}]
             _ :nothing))))

And now we can make a function for alerting players to other nearby players:

(def nearby
  (fn [p]
    (match p
      (danger 'n 'sr)
        (print! (string-append "DANGER! powerful player " n " with spell radius " (show sr) " is approaching!"))
      (prey 'c 'hp)
        (print! (string-append "A weak " c " with only " (show hp) " health is nearby! Attack!")))))

And calling

(nearby alice)

Will print "DANGER! powerful player Alice Smith with spell radius 134 is approaching!", but calling:

(nearby bob)

will print "A weak elf with only 55 health is nearby! Attack!".

Data Validation

Most of the entities that get manipulated and stored on chains are represented by structured radicle data. To maintain the integrety of the chain it can be important to refuse malformed/invalid data. To detect this, use validator functions. These are functions which take a piece of data as input and either throw an exception (thus cancelling the current transaction), or return the data unchanged. The radicle prelude contains functions for creating and combining validators.

For example let’s assume that we want to validate the :priority field on an issue entity, with the requirement that it be an integer \(p\) such that \(0 \leq p \leq 10\). To do this we can define a validator:

(def validator/priority
  (validator/and
    [(validator/type :number)
     (validator/pred "is integer"
                     integral?)
     (validator/pred "0 <= p <= 10"
                     (fn [p] (and (< -1 p) (< p 11))))]))

Here we have used validator/and to combine several validators together: the priority is valid only if it passes all three validators:

  • The first one checks that the value is a number.
  • The second uses validator/pred, which takes a description and a predicate,

and checks that the predicate holds. In this case we ensure that the priority is a whole number.

  • The third validator ensures the priority is in the required range.

Very often entities are represented using dicts with specific required keys. Validators for these entities can be created using the validator/keys function. For example to validate an issue we could use:

(def validator/issue
  (validator/keys
    {:id     (validator/and
               [(validator/type :string)
                (validator/pred "valid UUID"
                                 uuid?)])
     :author (validator/pred "valid public key"
                             public-key?)
     :title  (validator/and
               [(validator/type :string)
                (validator/pred "< 60 chars"
                                (fn [s] (< (string-length s) 60)))])
     :body   (validator/and
               [(validator/type :string)
                (validator/pred "valid markdown"
                                markdown?)
                (validator/pred "< 4000 chars"
                                (fn [s] (< (string-length s) 4000)))])
     :tags   (validator/every
               (validator/and
                 [(validator/type :string)
                  (validator/pred "< 40 chars"
                                  (fn [s] (< (string-length s) 40)))]))
     :priority validator/priority}))

This checks that an issue is a dict, with required keys:

  • :id that’s a valid UUID string,
  • :author that’s a valid public key,
  • :title that’s a string shorter than 60 characters,
  • :body that’s a markdown string less that 4000 characters,
  • :tag that’s a vector of strings, each shorted than 40 characters.
  • :priority that’s a integers \(p\) such that \(0 \leq p \leq 10\).

Because validators return their input if it is valid, we can just wrap any value which should be validated with the appropriate validator:

(fn [issue]
  (modify-ref
    issues
    (fn [is]
      (add-right (validator/issue issue) is))))

Testing

Radicle provides a :test macro which allows you to write tests next to your code.

(def not
  (fn [x] (if x #f #t)))

(:test "not"
   [ (not #t) ==> #f ]
   [ (not #f) ==> #t ]
   )

The test/prelude.rad script runs all tests defined in the prelude.

Test Definition

Each test definition consists of a test name and a list of steps

(:test "my test" step1 step2 ...)

Each step is a vector triple with the symbol ==> in the middle. For example

[ (not #t) ==> #f ]

When a test step is run the value left of ==> is evaluated in the environment captured at the definition site of the test. The resulting value is then compared with the right-hand side. The test passes if both are equal. The right-hand side is not evaluated.

If the evaluation of the left-hand side throws an error the test fails with the message produced by the error.

Changes to reference in a test step evaluation have no effect and all tests steps are run independently.

Test Setup

There is a special setup test step that allows you to change the environment that tests steps run in.

(:test "with setup"
  [ :setup (do
      (def foo 5)
      )]
  [ (+ foo 2) ==> 7 ]
  [ (- foo 2) ==> 3 ]
  )

Similar to test steps the body of the :setup step is evaluated in the environment of the definition site. Changes to the environment introduced by evaluating the setup code are then available in all test steps.

Running Tests

All tests defined with the :test macro are collected in the tests reference. Tests can be executed using the run-all function from the prelude/test module.

Issue machine tutorial

Adding an removing issues

In this example we will create a machine which stores some issues about a code repository. At the start it will only support adding and removing issues, but then we will add more features.

This is a literate radicle file, which means you can run it like this: stack exec doc -- docs/source/guide/Issues.lrad.

First we load the prelude:

(load! (find-module-file! "prelude.rad"))

We’ll use a ref to store the issues, as a dictionary from issue IDs to issues.

(def issues (ref {}))

To update the state of our issues we’ll use modify-ref, a function which takes a ref and a function. It updates the value held in the ref using the function and then returns the new value. We’ll be modifying issues a lot, so let’s create a function for that:

(def mod-issues
  (fn [f] (modify-ref issues f)))

Next we’ll define a function for creating a new issue. An issue is a dict which looks like:

{ :id     "ab12-xy23"
  :author "user-id-here"
  :title  "I want pattern matching"
  :body   "I can't continue using radicle without it."
}

The :id should be a valid UUID that is supplied by whoever submits the issue; we just check that the ID isn’t already used for another issue:

(def id-valid-and-free?
  (fn [id]
    (and (uuid? id)
         (not (member? id (read-ref issues))))))

To add an issue we just add it to the issues dict, using the :id as the key. Note that in the case of an error we use print! in this tutorial, but in a real machine one would throw to refuse the transaction.

(def new-issue
  (fn [i]
    (def id (lookup :id i))
    (if (id-valid-and-free? id)
      (mod-issues
        (fn [is] (insert id i is)))
      (print! "Issue ID was not free or was invalid."))))

Closing an issue is also easy–we just delete that issue from the dict:

(def close-issue
  (fn [id]
    (mod-issues (fn [is] (delete id is)))))

The behaviour we want from our machine is to accept some commands to perform issue-related tasks. We’ll store these commands in a dict, keyed by the command symbol. We put the whole thing in a ref so that we can add more commands later, or even update existing commands.

(def commands
  (ref
    {'create new-issue
     'close  close-issue}))

Now we create a function for handling these commands. We assume that the input is a list with at least 2 elements, the first of which is a symbol describing the command. We look up the command handler in the commands ref and then run it on the arguments using apply, which calls a function on a list of arguments. Finally we print the issues so that we can see how the state evolves.

(def process-command
  (fn [expr]
    (def command (head expr))
    (def args    (tail expr))
    (def do-this (lookup command (read-ref commands)))
    (apply do-this args)
    (print! (read-ref issues))
    :ok))

Now we update the eval. We will check for a special command update for updating the semantics of our machine (by executing arbitrary code), but otherwise we delegate to process-command. In general of course you wouldn’t want to leave such an update command with no restrictions; we’ll talk about that later.

(load! "rad/machine.rad")

(def eval
  (updatable-eval
    (fn [expr state]
      (eval-fn-app state 'process-command expr print!))))

Now we can start creating some issues. To generate some UUIDs, use the uuid! function at the REPL.

(create {:id     "f621caec-b0a3-4c5e-9fdd-147066a35af1"
         :author "james"
         :title  "Pattern matching"
         :body   "Pleeease"})

(create {:id     "a37e56bd-b66a-4f3f-af06-9eaeb4afdae9"
         :author "julian"
         :title  "Better numbers"
         :body   "The ids are floats!"})

Comments on issues

Now it would be nice to be able to comment on issues. For this we’ll just use a :comment field in the issue, which will be a vector of comments. The first thing we need to do therefore is add an empty vector of comments to all our current issues. To do this we’ll use the update command which allows us to run some arbitrary updating code:

(update
  (modify-ref issues
              (fn [is]
                (map-values (fn [i] (insert :comments [] i))
                            is))))

When creating issues we now need to be careful to add the comments:

(create {:id       "4a4d4479-468e-46e5-b026-1f84288aa682"
         :author   "Alice"
         :title    "Can issues have comments please?"
         :body     "So that we can talk about them."
         :comments []})

A more sophisticated handler might add an empty comments field if it doesn’t exist.

To add the commenting feature, first we will add a new function to add comments to issues.

(update
  (def add-comment
    (fn [issue-id comment]
      (mod-issues
        (fn [is]
          (modify-map issue-id
                      (fn [i]
                        (modify-map :comments
                                    (fn [cs] (add-right comment cs))
                                    i))
                      is))))))

TODO: modify-map should be renamed to modify-dict

(You’ll note that there is a lot of painful nested updating going on; later we will see how this can be made a lot easier with lenses.)

Adding this command to our commands dictionary will now make comments work:

(update
  (modify-ref
    commands
    (fn [cs] (insert 'comment add-comment cs))))

Now we can comment on issues:

(comment "a37e56bd-b66a-4f3f-af06-9eaeb4afdae9"
         {:author  "james"
          :comment "Yes that is a good idea."})

Validating data

It would be nice to validate issues and comments before they are added, e.g. checking if all the right keys exist.

TODO

Verifying authors

This machine has issues and comments coming from users but there is nothing that enforces that the issues and comments are actually submitted by these users. I (Alice) could easily submit the comment:

(update { :author “bobs-id” :comment “LGTM”})

to the machine and no one would know it wasn’t Bob. To remedy this we will use signatures. First of all we will assume that this machine has a unique ID:

(update
  (def machine-id "some-unique-id"))

Signatures are created using the gen-signature! function, which takes a secret key sk and a string msg. The signature sig that it returns can be used with the function verify-signature as follows: (verify-signature pk sig msg). If this returns #t then this tells us that the person who signed the message is the person who knows the secret key.

We create a function to verify that issues are valid:

(update
  (def issue-valid?
    (fn [i]
      (verify-signature (lookup :author i)
                        (lookup :signature i)
                        (string-append machine-id
                                       (lookup :id i)
                                       (lookup :title i)
                                       (lookup :body i))))))

Finally we modify the add-issue function. Note that we re-use our old add-issue function, just adding the extra layer of security:

(update
  (def add-verified-issue
    (fn [issue]
      (if (issue-valid? issue)
          (new-issue issue)
          ;; In a real machine we would throw, not print a message.
          (print! "The issue was not valid.")
          ))))

And we update our commands:

(update
  (modify-ref
    commands
    (fn [cs] (insert 'create add-verified-issue cs))))

Issues now have to be signed to be valid, here is some code one could run in another file to create such commands:

;; (def machine-id "some-unique-id")

;; (def kp (gen-key-pair! (default-ecc-curve)))

;; (def sk (lookup :private-key kp))
;; (def pk (lookup :public-key  kp))

;; (def make-issue
;;   (fn [title body]
;;     (def id (uuid!))
;;     (def msg
;;          (string-append machine-id
;;                         id
;;                         title
;;                         body))
;;     {:id        id
;;      :author    pk
;;      :title     title
;;      :body      body
;;      :signature (gen-signature! sk msg)}))

To generate a signed issue we can call, after loading that code into a REPL:

;; (make-issue "This issue has a verified author"
;;             "This is the body of the first issue with a verified author.")

And submit the result to the machine:

(create
  {:author [:public-key
   {:public_curve [:curve-fp
    [:curve-prime
    1.15792089237316195423570985008687907853269984665640564039457584007908834671663e77
    [:curve-common
    {:ecc_a 0.0
     :ecc_b 7.0
     :ecc_g [:Point
     5.506626302227734366957871889516853432625060345377759417550018736038911672924e76
     3.2670510020758816978083085130507043184471273380659243275938904335757337482424e76]
     :ecc_h 1.0
     :ecc_n 1.15792089237316195423570985008687907852837564279074904382605163141518161494337e77}]]]
    :public_q [:Point
    1.11054692487265016800292649812157504820937148585989526304621614094061257232989e77
    3.6059272479591624612420581719526072934261866833779446725219340058438651734523e76]}]
   :body "This is the body of the first issue with a verified author."
   :id "76dd218b-fbc1-4384-9962-8bfbec5da2a2"
   :signature [:Signature
   {:sign_r 1.07685492960818947345554835683887719269111710108141784367526228085824476440077e77
    :sign_s 3.740767076693925401519339475669557891360406440839428851888372253368058490896e76}]
   :title "This issue has a verified author"})

Signing comments would be done in a similar way.

Radicle Reference

This is the radicle reference document, with documentation for all functions which come as part of the standard distribution.

Primitive functions

Primitive functions are those that are built into the compiler. They are available on all machines but may be shadowed by later definitions. Those that end in a ! are only available locally, not on ‘pure’ machines.

*

Multiplies two numbers together.

+

Adds two numbers together.

-

Substracts one number from another.

/

Divides one number by another. Throws an exception if the second argument is 0.

<

Checks if a number is strictly less than another.

>

Checks if a number is strictly greater than another.

eq?

Checks if two values are equal.

apply

Calls the first argument (a function) using as arguments the elements of the the second argument (a list).

show

Returns a string representing the argument value.

throw

Throws an exception. The first argument should be an atom used as a label for the exception, the second can be any value.

exit!

Exit the interpreter immediately with the given exit code.

read-annotated

(read-anotated label s) parses the string s into a radicle value. The resulting value is not evaluated. The label argument is a string which is used to annotate the value with line numbers.

read-many-annotated

(read-many-annotated label s) parses a string into a vector of radicle values. The resulting values are not evaluated. The label argument is a string which is used to annotate the values with line numbers.

base-eval

The default evaluation function. Expects an expression and a radicle state. Return a list of length 2 consisting of the result of the evaluation and the new state.

ref

Creates a ref with the argument as the initial value.

read-ref

Returns the current value of a ref.

write-ref

Given a reference r and a value v, updates the value stored in r to be v and returns v.

match-pat

The most basic built-in pattern-matching dispatch function.

cons

Adds an element to the front of a sequence.

first

Retrieves the first element of a sequence if it exists. Otherwise throws an exception.

rest

Given a non-empty sequence, returns the sequence of all the elements but the first. If the sequence is empty, throws an exception.

add-right

Adds an element to the right side of a vector.

<>

Merges two structures together. On vectors and lists this performs concatenation. On dicts this performs the right-biased merge.

list

Turns the arguments into a list.

list-to-vec

Transforms lists into vectors.

vec-to-list

Transforms vectors to lists.

zip

Takes two sequences and returns a sequence of corresponding pairs. In one sequence is shorter than the other, the excess elements of the longer sequence are discarded.

map

Given a function f and a sequence (list or vector) xs, returns a sequence of the same size and type as xs but with f applied to all the elements.

length

Returns the length of a vector, list, or string.

foldl

Given a function f, an initial value i and a sequence (list or vector) xs, reduces xs to a single value by starting with i and repetitively combining values with f, using elements of xs from left to right.

foldr

Given a function f, an initial value i and a sequence (list or vector) xs, reduces xs to a single value by starting with i and repetitively combining values with f, using elements of xs from right to left.

drop

Returns all but the first n items of a sequence, unless the sequence is empty, in which case an exception is thrown.

sort-by

Given a sequence xs and a function f, returns a sequence with the same elements x of xs but sorted according to (f x).

take

Returns the first n items of a sequence, unless the sequence is too short, in which case an exception is thrown.

nth

Given an integral number n and xs, returns the nth element (zero indexed) of xs when xs is a list or a vector. If xs does not have an n-th element, or if it is not a list or vector, then an exception is thrown.

seq

Given a structure s, returns a sequence. Lists and vectors are returned without modification while for dicts a vector of key-value-pairs is returned: these are vectors of length 2 whose first item is a key and whose second item is the associated value.

dict

Given an even number of arguments, creates a dict where the 2i-th argument is the key for the 2i+1th argument. If one of the even indexed arguments is not hashable then an exception is thrown.

lookup

Given a value k (the ‘key’) and a dict d, returns the value associated with k in d. If the key does not exist in d then () is returned instead. If d is not a dict then an exception is thrown.

insert

Given k, v and a dict d, returns a dict with the same associations as d but with k associated to d. If d isn’t a dict or if k isn’t hashable then an exception is thrown.

delete

Given k and a dict d, returns a dict with the same associations as d but without the key k. If d isn’t a dict then an exception is thrown.

member?

Given v and structure s, checks if x exists in s. The structure s may be a list, vector or dict. If it is a list or a vector, it checks if v is one of the items. If s is a dict, it checks if v is one of the keys.

map-keys

Given a function f and a dict d, returns a dict with the same values as d but f applied to all the keys. If f maps two keys to the same thing, the greatest key and value are kept.

map-values

Given a function f and a dict d, returns a dict with the same keys as d but f applied to all the associated values.

string-append

Concatenates a variable number of string arguments. If one of the arguments isn’t a string then an exception is thrown.

string-length

DEPRECATED Use length instead. Returns the length of a string.

string-replace

Replace all occurrences of the first argument with the second in the third.

foldl-string

A left fold on a string. That is, given a function f, an initial accumulator value init, and a string s, reduce s by applying f to the accumulator and the next character in the string repeatedly.

type

Returns a keyword representing the type of the argument; one of: :atom, :keyword, :string, :number, :boolean, :list, :vector, :function, :dict, :ref.

atom?

Checks if the argument is a atom.

keyword?

Checks if the argument is a keyword.

boolean?

Checks if the argument is a boolean.

string?

Checks if the argument is a string.

number?

Checks if the argument is a number.

integral?

Checks if a number is an integer.

vector?

Checks if the argument is a vector.

list?

Checks if the argument is a list.

dict?

Checks if the argument is a dict.

file-module!

Given a file whose code starts with module metadata, creates the module. That is, the file is evaluated as if the code was wrapped in (module ...).

find-module-file!

Find a file according to radicle search path rules. These are: 1) If RADPATH is set, first search there; 2) If RADPATH is not set, search in the distribution directory 3) If the file is still not found, search in the current directory.

import

Import a module, making all the definitions of that module available in the current scope. The first argument must be a module to import. Two optional arguments affect how and which symbols are imported. (import m :as 'foo) will import all the symbols of m with the prefix foo/. (import m '[f g]) will only import f and g from m. (import m '[f g] :as 'foo') will import f and g from m as foo/f and foo/g. To import definitions with no qualification at all, use (import m :unqualified).

pure-state

Returns a pure initial radicle state. This is the state of a radicle chain before it has processed any inputs.

get-current-state

Returns the current radicle state.

set-current-state

Replaces the radicle state with the one provided.

get-binding

Lookup a binding in a radicle env.

set-binding

Add a binding to a radicle env.

set-env

Sets the environment of a radicle state to a new value. Returns the updated state.

state->env

Extract the environment from a radicle state.

timestamp?

Returns true if the input is an ISO 8601 formatted CoordinatedUniversal Time (UTC) timestamp string. If the input isn’t a string, an exception is thrown.

unix-epoch

Given an ISO 8601 formatted Coordinated Universal Time (UTC) timestamp, returns the corresponding Unix epoch time, i.e., the number of seconds since Jan 01 1970 (UTC).

from-unix-epoch

Given an integer the represents seconds from the unix epock return an ISO 8601 formatted Coordinated Universal Time (UTC) timestamp representing that time.

now!

Returns a timestamp for the current Coordinated Universal Time (UTC), right now, formatted according to ISO 8601.

to-json

Returns a JSON formatted string representing the input value. Numbers are only converted if they have a finite decimal expansion. Strings and booleans are converted to their JSON counterparts. Atoms and keywords are converted to JSON strings (dropping the initial ‘:’ for keywords). Lists and vectors are converted to JSON arrays. Dicts are converted to JSON objects as long as all the keys are strings, atoms, keywords, booleans or numbers.

from-json

Converts a JSON string into Radicle data. If the string is not valid JSON then :nothing is returned, otherwise [:just v] is returned where v is a Radicle representation of the JSON data.

uuid!

Generates a random UUID.

uuid?

Checks if a string has the format of a UUID.

default-ecc-curve

Returns the default elliptic-curve used for generating cryptographic keys.

verify-signature

Given a public key pk, a signature s and a message (string) m, checks that s is a signature of m for the public key pk.

public-key?

Checks if a value represents a valid public key.

gen-key-pair!

Given an elliptic curve, generates a cryptographic key-pair. Use default-ecc-curve for a default value for the elliptic curve.

gen-signature!

Given a private key and a message (a string), generates a cryptographic signature for the message.

get-args!

Returns the list of the command-line arguments the script was called with

put-str!

Prints a string.

get-line!

Reads a single line of input and returns it as a string.

load!

Evaluates the contents of a file. Each seperate radicle expression is evaluated according to the current definition of eval.

cd!

Change the current working directory.

stdin!

A handle for standard in.

stdout!

A handle for standard out.

stderr!

A handle for standard error.

read-file!

Reads the contents of a file and returns it as a string.

read-line-handle!

Read a single line from a handle. Returns the string read, or the keyword :eof if an EOF is encountered.

open-file!

Open file in the specified mode (:read, :write, :append, :read-write).

close-handle!

Close a handle

system!

(system! proc) execute a system process. Returns the dict with the form { :stdin maybe-handle      :stdout maybe-handle      :stderr maybe-handle      :proc prochandle    } Where maybe-handle is either [:just handle] or :nothing. Note that this is quite a low-level function; higher-level ones are more convenient.

wait-for-process!

Block until process terminates.

write-handle!

Write a string to the provided handle.

subscribe-to!

Expects a dict s (representing a subscription) and a function f. The dict s should have a function getter at the key :getter. This function is called repeatedly (with no arguments), its result is then evaluated and passed to f.

doc

Returns the documentation string for a variable. To print it instead, use doc!.

doc!

Prints the documentation attached to a value and returns (). To retrieve the docstring as a value use doc instead.

apropos!

Prints documentation for all documented variables in scope.

Prelude modules

These are the modules included in the radicle prelude and the functions these modules expose.

prelude/basic

Basic function used for checking equality, determining the type of a value, etc.

(or x y)

Returns x if x is not #f, otherwise returns y

(some xs)

Checks that there is a least one truthy value in a list.

(empty-seq? xs)

Returns true if xs is an empty sequence (either list or vector).

length

Returns the length of a vector, list, or string.

(maybe->>= v f)

Monadic bind for the maybe monad.

(maybe-foldlM f i xs)

Monadic fold over the elements of a sequence xs, associating to the left (i.e. from left to right) in the maybe monad.

(elem? x xs)

Returns true if x is an element of the sequence xs

tail

Backwards compatible alias for rest.

(read s)

Reads a radicle value from a string.

(read-many s)

Reads many radicle values from a string.

(<= x y)

Test if x is less than or equal to y.

prelude/patterns

Pattern matching is first-class in radicle so new patterns can easily be defined. These are the most essential.

(match-pat pat v)

The pattern matching dispatch function. This function defines how patterns are treated in match expressions. Atoms are treated as bindings. Numbers, keywords and strings are constant patterns. Dicts of patterns match dicts whose values at those keys match those patterns. Vectors of patterns match vectors of the same length, pairing the patterns and elements by index.

(_ v)

The wildcard pattern.

(/? p)

Predicate pattern. Takes a predicate function as argument. Values match against this pattern if the predicate returns a truthy value.

(/as var pat)

As pattern. Takes a variable and a sub-pattern. If the subpattern matches then the whole pattern matches and furthermore the variable is bound to the matched value.

(/cons x-pat xs-pat)

A pattern for sequences with a head and a tail.

(/nil v)

Empty-sequence pattern. Matches [] and (list)

(/just pat)

Pattern which matches [:just x].

(/member vs)

Matches values that are members of a structure.

prelude/bool

Functions for dealing with truthiness and #f.

(not x)

True if x is #f, false otherwise.

(and x y)

Returns y if x is not #f, otherwise returns x

(all xs)

Checks that all the items of a list are truthy.

(and-predicate f g)

Pointwise conjunction of predicates.

prelude/seq

Functions for manipulating sequences, that is lists and vectors.

(empty? seq)

True if seq is empty, false otherwise.

(seq? x)

Returns #t if x is a list or a vector.

(reverse xs)

Returns the reversed sequence xs.

(filter pred ls)

Returns ls with only the elements that satisfy pred.

(take-while pred ls)

Returns all elements of a sequence ls until one does not satisfy pred

(starts-with? s prefix)

Returns #t if prefix is a prefix of the sequence s. Also works for strings

(/prefix prefix rest-pat)

Matches sequences that start with prefix and bind the rest of that sequence to rest-pat. Also works for strings.

(concat ss)

Concatenate a sequence of sequences.

prelude/list

Functions for creating lists. See also prelude/seq.

nil

The empty list.

(range from to)

Returns a list with all integers from from to to, inclusive.

prelude/strings

String manipulation functions.

(intercalate sep strs)

Intercalates a string in a list of strings

(unlines x)

Concatenate a list of strings, with newlines in between.

(unwords x)

Concatenate a list of strings, with spaces in between.

(split-by splitter? xs)

Splits a string xs into a list of strings whenever the function splitter? returns true for a character.

(words xs)

Splits a string xs into a list of strings by whitespace characters.

(lines xs)

Splits a string xs into a list of strings by linebreaks.

(map-string f xs)

Returns a string consisting of the results of applying f to each character of xs. Throws a type error if f returns something other than a string

(reverse-string str)

Reverses str. E.g.: (reverse-string "abc") == "cba".

(ends-with? str substr)

True if str ends with substr

(pad-right-to l word)

Appends the word with whitespace to get to length l. If word is longer than l, the whole word is returned without padding.

prelude/error-messages

Functions for user facing error messages. Functions should either have a descriptive name or additional comment so that the text can be edited without knowledge of where they are used. To verify changes, tests can be run with stack exec -- radicle test/all.rad

(missing-arg arg cmd)

Used for command line parsing when an argument to a command is missing.

(too-many-args cmd)

Used for command line parsing when there are too many arguments passed to a command.

(missing-arg-for-opt opt valid-args)

Used for command line parsing when an option requires an argument.

(invalid-arg-for-opt arg opt valid-args)

Used for command line parsing when the argument for an option is invalid.

(invalid-opt-for-cmd opt cmd)

Used for command line parsing when the option for a given command is unkown

(dir-already-exists dir-name)

rad project checkout is aborted, if there is already a directory with the name of the project dir-name in the current directory.

(git-clone-failure origin name)

rad project checkout is aborted, if cloning the repo name form origin failed.

(upstream-commit-failure)

rad project init is aborted when creating an empty commit failed in preparation to setting the upstream master branch.

(upstream-push-failure)

rad project init is aborted when pushing the empty commit failed while setting the upstream master branch.

(item-not-found item item-number)

Any command on a specific patch/issue aborts if it does not exist.

(whole-item-number item)

Any command on a specific patch/issue aborts if the provided item-number is not a whole number.

(missing-item-number item action)

Any command on a specific patch/issue aborts if the item-number is not provided.

(state-change-failure item state)

On changing the state of a patch/issue if the daemon returned an error.

(no-number-returned item)

On creating a patch/issue, when the creation was successful, but no patch/issue number was returned.

(unknown-command cmd)

An unknown command for an app. E.g. rad issue foobar

(unknown-commit commit)

rad patch propose aborts if the provided commit is unknown.

(parent-commit-not-master commit)

rad patch propose aborts if the provided commit is unknown.

(checkout-new-branch-failure branch)

rad patch checkout aborts if creating and switching to the patch branch fails.

(checkout-master-failure)

rad patch accept aborts if checking out the master branch fails.

(applying-patch-failure)

rad patch checkout aborts if applying the patch to the patch branch fails. Conflicts have to be resolved manually.

(applying-accepted-patch-failure)

rad patch accept aborts if applying the patch to master fails. Conflicts have to be resolved manually as well as pushing the commit.

(push-patch-failure)

rad patch accept aborts if pushing the patch failed.

(missing-key-file)

Any request to the machine is aborted, when the key file can’t be found.

(rad-ipfs-name-publish-failure stderr)

Printed when the rad ipfs name publish command in init-git-ipfs-repo in rad-project fails. Takes stderr of the command as an argument.

(rad-ipfs-key-gen-failure stderr)

Printed when the rad ipfs key gen command in init-git-ipfs-repo in rad-project fails. Takes stderr of the command as an argument.

(process-exit-error command args exit-code stderr)

Printed when the a sub process exits with a non-zero exit code. Includes the stderr output in the message.

prelude/dict

Functions for manipualting dicts.

(dict-from-seq xs)

Creates a dictionary from a list of key-value pairs.

(keys d)

Given a dict d, returns a vector of its keys.

(values d)

Given a dict d, returns a vector of its values.

(rekey old-key new-key d)

Change the key from old-key to new-key in a dict d. If new-key already exists, it is overwritten.

(modify-map k f d)

Given a key k, a function f and a dict d, applies the function to the value associated to that key.

(delete-many ks d)

Delete several keys ks from a dict d.

(lookup-default key default dict)

Like lookup but returns default if the key is not in the map.

(lookup-maybe key dict)

Like lookup but returns [:just x] if the key is not in the map and :nothing otherwise.

(safe-modify-map k f d)

Modifies the association of a value to a key k in a dict d. The function f will receive [:just v] if (eq? (lookup k d) v), otherwise it will receive :nothing. It should return [:just new-v] to change the value, and :nothing to remove it.

(group-by f xs)

Partitions the values of a sequence xs according to the images under f. The partitions are returned in a dict keyed by the return value of f.

prelude/io

Some basic I/O functions.

(shell! command to-write)

Executes command using the shell with to-write as input. Stdout and stderr are inherited. WARNING: using shell! with unsanitized user input is a security hazard! Example: (shell! "ls -Glah" "").

(process! command args to-write)

Executes command using execvp with to-write as input. Stdout and stderr are inherited. See man exec for more information on execvp. Returns :ok if the process exited normally and [:error n] otherwise. Example: (process! "ls" ["-Glah"] "").

(read-line!)

Read a single line of input and interpret it as radicle data.

(read-file-value! file)

Read a single radicle value from a file.

(read-file-values! file)

Read many radicle values from a file.

(shell-with-stdout! command to-write)

Like shell!, but captures the stdout and returns it.

(shell-no-stdin! command to-write)

Like shell!, but inherits stdin. WARNING: using shell! with unsanitized user input is a security hazard! Example: (shell-no-stdin! "ls -Glah").

(write-file! filename contents)

Write contents to file filename.

(process-with-stdout! command args to-write)

Like process!, but captures stdout.

(process-with-stdout-stderr-exitcode! command args to-write)

Like process-with-stdout!, but returns a vec [stdout stderr exitcode]. exitcode is either :ok or [:error n] where n is a number.

(process-with-stdout-strict! command args to-write)

Like process-with-stdout!, but prints an error message and exits if the command fails.

(init-file-dict! file)

Initiate a file with an empty dict, but only if the file doesn’t already exist.

(read-file-key! file k)

Read a file key. Assumes that the file contents is a serialised dict.

(write-file-key! file k v)

Write a key to a file. Assumes that the file contents is a serialised dict.

(delete-file-key! file k)

Delete a key from a file. Assumes that the file contents is a serialised dict.

(ls!)

List the contents of the current working directory

(modify-file! file f)

Modified the value stored in a file according to the function f.

(install-fake-filesystem! files)

Installs a fake for read-file! that simulates the presence of files in the files dictionary.

If (read-file! path) is called andpathis a key infilesthen the value fromfilesis returned. Otherwise the originalread-file!` is used.

This requires the prelude/test/primitive-stub script to be loaded.

(prompt! prompt)

Ask for user input with a prompt.

prelude/exception

Tests for exceptions.

prelude/set

Sets, built using dicts.

empty

An empty set.

(insert x s)

Insert a value into a set.

(delete x s)

Delete a value from a set.

member?

Query if a value is an element of a set.

(to-vec s)

Convert a set to a vector.

(from-seq xs)

Create a set from a sequence.

(key-set d)

The set of keys of a dict.

(subset? xs ys)

Checks if xs is a subset of ys.

prelude/ref

Functions for dealing with reference cells.

(modify-ref r f)

Modify r by applying the function f. Returns the new value.

prelude/lens

Functional references.

(make-lens g s)

Makes a lens out of a getter and a setter.

(view lens target)

View a value through a lens.

(set lens new-view target)

Set a value though a lens.

id-lens

The identity lens.

(.. lens1 lens2)

Compose two lenses.

(... lenses)

Compose multiple lenses.

(over lens f target)

Modify a value through a lens.

(@ k)

Returns a lens targetting keys of dicts.

(@def k default)

Returns a lens targetting keys of dicts with a default value for getting if the key does not exist in the target.

(@nth n)

Lenses into the nth element of a vector

(view-ref r lens)

Like view, but for refs.

(set-ref r lens v)

Like set, but for refs.

(over-ref r lens f)

Like over, but for refs.

prelude/io-utils

IO-related utilities

(fzf-select! xs)

Select one of many strings with fzf. Requires that fzf be on the path. Returns [:just x] where x is the selected string, or :nothing if nothing was selected.

(edit-in-editor! orig)

Open $EDITOR on a file prepopulated with orig. Returns the contents of the edited file when the editor exits.

(get-git-config! key)

Get the value associated with a key in git config.

(set-git-config! key value)

Set the value associated with a key in git config.

(get-git-commit-data! format commit)

Get data from a commit via show specified by format

(get-git-username!)

Get the user name stored in git config.

(process-git-with-exit! args msg)

Processes a git command args. If it fails, the message msg is shown and the process exits, otherwise :ok is passed.

(base-path!)

Returns the base path for storage of radicle related config files. By default this is $HOME/.config/radicle. This can be adjusted by setting $XDG_CONFIG_HOME.

prelude/key-management

Providing functions for creating and reading key pairs for signing send commands. Per default, key pairs are stored in $HOME/.config/radicle/my-keys.rad this can be adjusted by setting $XDG_CONFIG_HOME.

(read-keys!)

Reads the keys stored in my-keys.rad or returns :nothing if the file doesn’t exist.

(get-keys!)

Like read-keys but prints an error message and exits the process if no key file was found.

(create-keys!)

Creates a new key pair and stores it in my-keys.rad. Returns the full absolute path of the created file.

(set-fake-keys! keys)

Bypass reading the keys from my-keys.rad, using instead the provided keys. This is intended for testing.

(use-fake-keys!)

Bypass reading the keys from my-keys.rad, using newly-generated ones. This is intended for testing.

prelude/machine

Functions for simulating remote machines.

(updatable-eval sub-eval)

Given an evaluation function f, returns a new one which augments f with a new command (update expr) which evaluates arbitrary expression using base-eval.

(eval-fn-app state f arg cb)

Given a state, a function, an argument and a callback, returns the result of evaluating the function call on the arg in the given state, while also calling the callback on the result.

(send-prelude! machine-id)

Send the pure prelude to a machine.

(new-machine!)

Creates a new machine. Returns the machine name.

(send-code! machine-id filename)

Send code from a file to a remote machine.

(send! machine-id inputs)

Update a machine with the vector of inputs to evaluate. Returns a vector with the evaluation results.

(query! machine-id expr)

Send an expression to be evaluated on a machine. Does not alter the machine.

(install-remote-machine-fake)

Install test doubles for the send!, query!, and new-machine! primitives that use a mutable dictionary to store RSMs. Requiresrad/test/stub-primitives` to be loaded

(send-signed-command! machine machine-id cmd payload)

Send a command signed by the keys in my-keys.rad.

(catch-daemon! f)

Catches all radicle-daemon related errors and just prints them out to the user.

prelude/state-machine

An eval for running a state-machine with an updatable transition function.

prelude/validation

Functions for creating or combining validators, which are functions which return the input unchanged or throw with an error message. These can be used for checking data before accepting it onto a chain.

(= x)

Given x, returns a validator that checks for equality with x.

(member xs)

Given a structure, returns a validator which checks for membership in the structure.

(and vs)

Given a sequence of validators vs, returns a new validator which, given a value, checks if it conforms to all the validators in vs.

(or vs)

Given a vector of validators vs, returns a new validator which, given a value, checks if it conforms to at least one of the vs.

(type t)

Checks that a value has a type. Expects a keyword describing the type, as returned by the type function.

(pred name p)

Given a description and a predicate, returns a validator that checks if the predicate is true.

(integral n)

Validator for whole numbers.

(optional-key k v)

Given a key k and a validator v, returns a validator which checks that the value associated to k in a dict conforms to v. If the key is absent, the validator passes.

(contains k)

Given a value, returns a validator which checks for membership of that value.

(contains-all ks)

Given a vector of keys, returns a validator which checks that a structure contains all of them.

(contains-only ks)

Validator which checks that a dict only contains a subset of a vector of keys.

(key k v)

Combines existence and validity of a key in a dict.

(optional-keys ks)

Given a dict associating keys to validators, returns a validator which checks that the values associated to those keys in a dict conform to the corresponding validators.

(keys d)

Given a dict d, returns a validator which checks that a dict contains all the keys that d does, and that the associated values a valid according to the associated validators.

(every v)

Given a validator, creates a new validator which checks that all the items in a sequence conform to it.

(uuid x)

Validates UUIDs.

(signed x)

Checks that a value is a dict with :signature and :author keys, and that the signature is valid for the rest of the dict for that author. The rest of the dict is turned into a string according to show.

(timestamp x)

A validator which checks if a string is an ISO 8601 formatted Coordinated Universal Time (UTC) timestamp.

(string-of-max-length max-len)

A validator which checks that it’s argument is a string and less than the specified length.

(always-valid x)

A validator that is always valid.

prelude/util

Utility functions. For the moment just a counter.

(make-counter)

Creates a stateful counter. Returns a dict with two keys: the function at :next-will-be will return the next number (without incrementing it), while the function at :next increments the number and returns it.

Indices and tables