Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Babashka and the Small Clojure Interpreter @ ClojureD 2020

Babashka and the Small Clojure Interpreter @ ClojureD 2020

Michiel Borkent

February 29, 2020
Tweet

More Decks by Michiel Borkent

Other Decks in Programming

Transcript

  1. • 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! +
  2. 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)

  3. • 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
  4. 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
  5. 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
  6. Shell interaction $ ls | bb -i '(filter #(-> %

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

    .isDirectory))"
 
 $ ls | bb -i '(filter is-dir? *input*)' ("doc" "examples" "logo" ...)
  8. 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))
  9. 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, ...
  10. 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
  11. 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]
  12. 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
  13. 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)
  14. 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
  15. 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}
  16. 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 }
  17. 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}"
  18. Companies* using** babashka / sci Github #254 Add your company

    to the list: * At least one person
 ** Or evaluating
  19. Conclusion Clojure might not be the best language for everything,

    like scripting Clojure is the best language for scripting.