Examples
Slightly larger pieces, each run against v0.63.0 with the output it produced.
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))Binary search
Two arities — one to start, one to carry the state — and recur for the loop.
(defn guesses
([low high target] (guesses low high target 1))
([low high target tries]
(let [middle (+ low (integer/divide (- high low) 2))]
(cond (= middle target) tries
(< middle target) (recur (+ middle 1) high target (+ tries 1))
true (recur low (- middle 1) target (+ tries 1))))))
(guesses 1 100 73) ; => 6
(guesses 1 1000000 1000000) ; => 20Validation 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.
(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)))(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 allA 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.