Skip to content

Instantly share code, notes, and snippets.

@lagenorhynque
Last active November 11, 2022 07:12
Show Gist options
  • Select an option

  • Save lagenorhynque/088fa81ad8bb081ec6cd4db3085f7f2f to your computer and use it in GitHub Desktop.

Select an option

Save lagenorhynque/088fa81ad8bb081ec6cd4db3085f7f2f to your computer and use it in GitHub Desktop.
ClojureScript: The Good Parts

ClojureScript

The Good Parts


カマイルカ🐬/laʒenɔʁɛ̃k/

(defprofile lagénorhynque
  :name         "Kent OHASHI"

  :languages    [Clojure Haskell Python Scala
                 English français Deutsch русский]

  :interests    [programming language-learning mathematics]

  :contributing [github.com/japan-clojurians/clojure-site-ja])

twitter icon


Lisp × JavaScript

lisp warning


What is ClojureScript?


  • compiler for Clojure that targets JavaScript

  • simple and powerful functional Lisp

clojurescript logo


Use Cases


Try CLJS with Lumo

$ npm install -g lumo-cljs
$ lumo
cljs.user=> (defn hello [& {:keys [to]
       #_=>                 :or {to "world"}}]
       #_=>   (println (str "Hello, " to "!")))
#'cljs.user/hello

cljs.user=> (hello)
Hello, world!
nil

cljs.user=> (hello :to "ClojureScript")
Hello, ClojureScript!
nil

Good Parts


Lisp

lisp alien


S-expressions (sexp)

  • simple rule: (op arg ...)

    • various kinds of
      • functions
      • macros
      • special forms
  • structural editing


Collection Literals

cljs.user=> '(1 2 3)          ; list
(1 2 3)

cljs.user=> [1 2 3]           ; vector
[1 2 3]

cljs.user=> #{1 2 3}          ; set
#{1 2 3}

cljs.user=> {:a 1 :b 2 :c 3}  ; map
{:a 1, :b 2, :c 3}

cf. function definition

(defn hello [& {:keys [to]
                :or {to "world"}}]
  (println (str "Hello, " to "!")))

Lisp Macros

  • compile-time metaprogramming

  • code as data

sexp -> sexp


  • e.g. for macro (sequence comprehension)
;; ClojureScript
cljs.user=> (for [x (range 10)
       #_=>       :when (odd? x)]
       #_=>   (* x x))
(1 9 25 49 81)

cljs.user=> (macroexpand-1
       #_=>  '(for [x (range 10)
       #_=>         :when (odd? x)]
       #_=>     (* x x)))
(cljs.core$macros/let
 [iter__9116__auto__
  (cljs.core$macros/fn
   ,,,
# Python
>>> [x ** 2 for x in range(10) if x % 2 != 0]
[1, 9, 25, 49, 81]
-- Haskell
> [x ^ 2 | x <- [0..9], odd x]
[1,9,25,49,81]

REPL-driven Development

Create a ClojureScript project with Leiningen

$ brew install leiningen
$ lein new figwheel cljs-demo  # e.g. "figwheel" template
$ cd cljs-demo
$ lein figwheel  # or `cider-jack-in-clojurescript` on Emacs

  • editor-integrated REPL (e.g. Emacs & CIDER)

repl driven development 1


repl driven development 2


  • browser REPL for front-end development

repl driven development 3


Changes are automatically loaded in the browser

repl driven development 4


Functional

Programming

clojure logo


Immutable Persistent Collections

  • no mutations, no side effects

  • high performance

ljs.user=> (conj [1 2] 3)             ; add an element to vector
[1 2 3]

cljs.user=> (conj '(1 2) 3)           ; add an element to list
(3 1 2)

cljs.user=> (conj #{1 2} 3)           ; add an element to set
#{1 2 3}

cljs.user=> (assoc {:a 1 :b 2} :c 3)  ; add an entry to map
{:a 1, :b 2, :c 3}

Map and Sequence

as Core Abstractions

  • maps: get, assoc

  • sequences: first, rest, cons

    • lazy sequences
  • rare to define something like classes or algebraic data types

  • few data abstractions and many functions


  • e.g. maps for modelling entities
cljs.user=> (ns geometry.sphere)
nil
geometry.sphere=> (defn surface-area [{::keys [radius]}]
             #_=>   (* 4 Math/PI (Math/pow radius 2)))
#'geometry.sphere/surface-area
geometry.sphere=> (defn volume [{::keys [radius]}]
             #_=>   (* 4/3 Math/PI (Math/pow radius 3)))
#'geometry.sphere/volume

geometry.sphere=> #::{:radius 2}
#:geometry.sphere{:radius 2}
geometry.sphere=> (surface-area #::{:radius 2})
50.26548245743669
geometry.sphere=> (volume #::{:radius 2})
33.510321638291124

  • e.g. typical sequence manipulations
cljs.user=> (defn leibniz [n-terms]
       #_=>   (->> (iterate #(+ % 2) 1)
       #_=>        (map / (cycle [1 -1]))
       #_=>        (take n-terms)
       #_=>        (apply +)
       #_=>        (* 4.0)))
#'cljs.user/leibniz

cljs.user=> (leibniz 1000)
3.140592653839794
cljs.user=> (leibniz 10000)
3.1414926535900345
cljs.user=> (leibniz 100000)
3.1415826535897198

Data > Functions > Macros



Common mistakes with maps ...

;; typo in key name
geometry.sphere=> (surface-area #::{:radias 2})
0

;; incorrect value type
geometry.sphere=> (volume #::{:radius "2"})
33.510321638291124

Introduce clojure.spec

geometry.sphere=> (require '[cljs.spec.alpha :as s
             #_=>            :include-macros true])
nil
geometry.sphere=> (s/def ::radius (s/and number? pos?))
:geometry.sphere/radius
geometry.sphere=> (s/def ::sphere (s/keys :req [::radius]))
:geometry.sphere/sphere
geometry.sphere=> (s/fdef surface-area
             #_=>   :args (s/cat :sphere ::sphere)
             #_=>   :ret number?)
geometry.sphere/surface-area
geometry.sphere=> (s/fdef volume
             #_=>   :args (s/cat :sphere ::sphere)
             #_=>   :ret number?)
geometry.sphere/volume

Instrument specs

geometry.sphere=> (require '[cljs.spec.test.alpha :as stest
             #_=>            :include-macros true])
nil
geometry.sphere=> (stest/instrument)
[geometry.sphere/surface-area geometry.sphere/volume]

※ add test.check as a dependency (cf. CLJS-1792)

# for example
$ lumo -c src:~/.m2/repository/org/clojure/test.check-0.9.0.jar

Spec-instrumented surface-area function

geometry.sphere=> (surface-area #::{:radius 2})
50.26548245743669

geometry.sphere=> (surface-area #::{:radias 2})
Call to #'geometry.sphere/surface-area did not conform to spec:
In: [0] val: #:geometry.sphere{:radias 2} fails
  spec: :geometry.sphere/sphere
  at: [:args :sphere]
  predicate: (contains? % :geometry.sphere/radius)
:cljs.spec.alpha/spec  #object[cljs.spec.alpha.t_cljs$spec$alpha
10508]
:cljs.spec.alpha/value  (#:geometry.sphere{:radias 2})
:cljs.spec.alpha/args  (#:geometry.sphere{:radias 2})
:cljs.spec.alpha/failure  :instrument
,,,

Spec-instrumented volume function

geometry.sphere=> (volume #::{:radius 2})
33.510321638291124

geometry.sphere=> (volume #::{:radius "2"})
Call to #'geometry.sphere/volume did not conform to spec:
In: [0 :geometry.sphere/radius] val: "2" fails
  spec: :geometry.sphere/radius
  at: [:args :sphere :geometry.sphere/radius]
  predicate: number?
:cljs.spec.alpha/spec  #object[cljs.spec.alpha.t_cljs$spec$alpha
10508]
:cljs.spec.alpha/value  (#:geometry.sphere{:radius "2"})
:cljs.spec.alpha/args  (#:geometry.sphere{:radius "2"})
:cljs.spec.alpha/failure  :instrument
,,,

Land of Lisp invades JS world!!

lisp alien

lisp alien


Further Reading


Clojure


ClojureScript


Reagent/re-frame

#!/usr/bin/env bash
# npm install -g reveal-md
reveal-md clojurescript-the-good-parts.md --theme night --highlight-theme monokai-sublime $@
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment