joetjen.net
EN DE
Programming language

Examples

Slightly larger pieces, each run against v0.63.0 with the output it produced.

In development · v0.63

Word frequencies

A pipeline over the standard library. Every step takes the text or collection first, so -> threads it through.

(defn top-words "The `n` most frequent words in `text`."
  [text n] :spec [string integer -> seq]
  (-> text
      string/lower-case
      (string/split " ")
      (seq/remove string/blank?)
      seq/frequencies
      map/to-pairs
      (seq/sort-by (fn [#(_word count)] (- count)))
      (seq/take n)))

(top-words "the cat and the hat and the bat" 2)
; => (#("the" 3) #("and" 2))

Validation that reports every error

Map destructuring in the parameter list, with for the happy path and result/collect to gather every failure instead of stopping at the first. Prices are decimals, so the total is exact.

(defn- positive [field value]
  (if (> value 0)
    #(:ok value)
    #(:error (str field " must be positive"))))

(defn check-order [{:qty qty :price price}]
  (with [#(:ok checked) (result/collect [(positive "qty" qty) (positive "price" price)])]
    #(:ok {:qty (seq/first checked) :price (seq/nth checked 1) :total (* qty price)})))

(check-order {:qty 3 :price 1.25M})
; => #(:ok {:total 3.75M, :qty 3, :price 1.25M})
(check-order {:qty 0 :price -1})
; => #(:error ("qty must be positive" "price must be positive"))

A supervised actor

Let it crash, in Praxis: the repository’s actors example, with its docstrings and comments left out. A counter in its own process, a supervisor that restarts it, and a program that asks it to fail and handles nothing.

src/actors/counter.prax
(ns actors.counter "A counter that lives in its own process." (:behaviour praxis.actor))

(defn init [from] :spec [integer -> tuple]
  #(:ok from))

(defn handle-call [_message _from state] :spec [any any integer -> tuple]
  #(:reply state state))

(defn handle-cast [message state] :spec [keyword integer -> tuple]
  (cond
    (= message :bump) #(:noreply (+ state 1))
    true (throw :asked-to-fail)))
src/actors/tree.prax
(ns ^{:platforms [:beam]} actors.tree "The supervisor over the counter." (:behaviour praxis.supervisor))
(require [praxis.supervisor :as sup])
(require [actors.counting :as _counting])

(defn start-link [] :spec [-> tuple]
  (sup/start-link actors.tree :ignored))

(defn init [_argument] :spec [any -> tuple]
  (sup/tree (sup/flags :one-for-one)
            [(sup/child actors.counting 0 {:id actors.counter})]))

The callers’ side, actors.counting, is a namespace of its own, so the same counter also runs on node: a namespace that waits compiles to generators there, and a callback must not. The program starts the tree, bumps the counter three times, casts :boom, and asks again. prx run:

counted to 3, in #<pid 0.171.0>
asking it to fail...

21:55:24.212 [error] actors.counter stopped: #(:bad_return_value #(:asked-to-fail {}))
It was casting :boom
back at 0, in #<pid 0.172.0>
the program never handled the failure at all

A new pid under the same name is the supervisor at work, and the count is back at 0 because a restarted child starts from its argument again: the state of a process that failed is not to be trusted.

One source, two machines

The repository’s portable example is a single program run twice — prx run on the BEAM, and prx compile --target js --suite node followed by node — with the two outputs compared by diff. Its fourteen lines are the places two machines most easily stop agreeing: ratios, decimals, integers past 2⁵³, one order across every kind of value, bitstrings, and where a string stops being a binary. The JavaScript build writes one ES module per namespace, a source map and TypeScript declarations generated from the specs. Across the whole conformance suite, the BEAM and node agree on 1031 of 1031 cases.