Slide 1

Slide 1 text

And the Small Clojure Interpreter Michiel Borkent @borkdude 2020-02-29

Slide 2

Slide 2 text

• CLI tools with instant startup! (< 10ms) • clj-kondo: a linter for Clojure that sparks joy • jet: convert between JSON, EDN and Transit • Jan Stępień, ClojureD 2019 • No eval: dynamic classloader not supported! +

Slide 3

Slide 3 text

DSL -> scripting • Added a query DSL to jet, a GraalVM CLI:
 
 $ jet --query '(map :id)' <<< '[{:id 1} {:id 2}]'
 [1 2] • Extend this DSL to significant subset of Clojure?
 
 $ ??? '(->> [{:id 1} {:id 2}] (map :id))'
 (1 2)


Slide 4

Slide 4 text

• Native Clojure scripting tool, single binary, no JVM • Can be used to replace “the grey areas” of bash • Installable via script, brew (macOS, linux), aur (linux), scoop (Windows) $ time bb '(+ 1 2 3)'
 6
 0.00s user 0.00s system 67% cpu 0.013 total

Slide 5

Slide 5 text

CLJ scripting Runtime Impl Startup* Interop Windows Execution Threads clojure JVM Java ~1.5s + + Compiled + planck JSCore CLJS / JS ~ 1s + - Compiled - joker Native Go ~50ms** - + Interpreted - babashka Native GraalVM ~13ms + + Interpreted + *) https://stuartsierra.com/2019/12/21/clojure-start-time-in-2019 **) faster startup in progress

Slide 6

Slide 6 text

Babashka goals • Low latency Clojure scripting alternative for JVM Clojure • Easy installation: README ⟶ grab binary ⟶ run within seconds • Familiar and portable: JVM clojure ⟷ bb, #{linux, macOS, Windows} • Interop with commonly used classes (System, File, java.time.*, java.nio.*) • Multi-threading support (pmap, future) • Batteries included (tools.cli, cheshire) + external libraries

Slide 7

Slide 7 text

Shell interaction $ ls | bb -i '(filter #(-> % io/file .isDirectory) *input*)' ("doc" "examples" "logo" ...)

Slide 8

Slide 8 text

Predefine functions $ export BABASHKA_PRELOADS="(defn is-dir? [f] (-> f io/file .isDirectory))"
 
 $ ls | bb -i '(filter is-dir? *input*)' ("doc" "examples" "logo" ...)

Slide 9

Slide 9 text

Scripts $ pst.clj
 04:58 #!/usr/bin/env bb (def now (java.time.ZonedDateTime/now)) (def LA-timezone (java.time.ZoneId/of "America/Los_Angeles")) (def LA-time (.withZoneSameInstant now LA-timezone)) (def pattern (java.time.format.DateTimeFormatter/ofPattern "HH:mm")) (println (.format LA-time pattern))

Slide 10

Slide 10 text

Also works on Windows

Slide 11

Slide 11 text

Included libs / namespaces • clojure.{core, edn, java.shell, java.io, set, string, test, walk} • clojure.tools.cli • clojure.core.async (thread ops work, go WIP) • clojure.data.csv • cheshire.core (JSON) • babashka.{wait, signal} (wait for port to open or file to exist) • TBD: clojure.xml, yaml, http client, ...

Slide 12

Slide 12 text

Compatible libs and scripts • spartan.spec: clojure.spec.alpha (1) for bb • deps.clj: a port of the clojure bash script to babashka • clj-http-lite: lighter fork of cli-http-lite • medley: "missing" clojure utility functions • regal: create regular expressions from EDN/hiccup • limit-break: REPL debugging tool • clojure-csv: another CSV library

Slide 13

Slide 13 text

Classpath ;; spec.clj
 (require '[spartan.spec :as s])
 (s/explain (s/cat :x int? :y keyword?) [1 #{:foo}]) $ BABASHKA_CLASSPATH=$(clojure -Spath ...) $ bb spec.clj
 #{:foo} - failed: keyword? in: [1] at: [:y]

Slide 14

Slide 14 text

Decomplecting babashka Libraries coming out of babashka: • edamame: EDN/Clojure parser • sci: a Small Clojure Interpreter

Slide 15

Slide 15 text

edamame (def parsed (edamame/parse-string "#(+ 1 2 %)" {:fn true})) ;;=> (fn* [%1] (+ 1 2 %1)) (meta parsed) ;;=> {:row 1, :col 1, :end-row 1, :end-col 11} - EDN/code parser - GraalVM compatible (no eval!) - location metadata - opt-in code-like features

Slide 16

Slide 16 text

Small Clojure Interpreter (def f (sci/eval-string "#(+ 1 2 %)")) (f 1) ;;=> 4 - Clojure interpreter - Works on JVM / GraalVM / JS - Sandboxing
 - Works in CLJS advanced compiled apps
 - Comes with Java and JS APIs (available on NPM)

Slide 17

Slide 17 text

Malli: serializable schemas (def my-schema [:and [:map [:x int?] [:y int?]] [:fn '(fn [{:keys [x y]}] (> x y))]]) (m/validate my-schema {:x 1, :y 0}) ; => true (m/validate my-schema {:x 1, :y 2}) ; => false

Slide 18

Slide 18 text

Function CLI args $ jet --edn-reader-opts "{:readers {'foo (fn [x] [:foo x])}}" \
 <<< '#foo{:a 1}'
 [:foo {:a 1}] $ jet --from json --keywordize '(comp keyword str/upper-case)' \
 <<< '{"a": 1}' {:A 1}

Slide 19

Slide 19 text

Sci from JavaScript https://observablehq.com/@jeroenvandijk/untitled/5 $ npm install @borkdude/sci
 $ node
 > const { evalString, toJS } = require('@borkdude/sci');
 > x = evalString("(assoc {:a 1} :b 2)")
 > toJS(x) { a: 1, b: 2 }

Slide 20

Slide 20 text

Sci: adding libs (require '[cheshire.core :as json])
 
 (def sci-opts
 {:namespaces
 {'cheshire.core
 {'generate-string json/generate-string}}}) 
 (sci/eval-string "(require '[cheshire.core :as json]) (json/generate-string {:a 1})" sci-opts)
 ;;=> "{\"a\":1}"

Slide 21

Slide 21 text

Projects using sci

Slide 22

Slide 22 text

Companies* using** babashka / sci Github #254 Add your company to the list: * At least one person
 ** Or evaluating

Slide 23

Slide 23 text

Conclusion Clojure might not be the best language for everything, like scripting Clojure is the best language for scripting.

Slide 24

Slide 24 text

Thank you https://github.com/borkdude/babashka
 https://github.com/borkdude/sci And the Small Clojure Interpreter