ClojureScript
for the web
Michiel Borkent
@borkdude
Øredev, November 6th 2014
Michiel Borkent (@borkdude)
● Clojure(Script) developer at
● Clojure since 2009
● Former lecturer, taught Clojure
Agenda
● Why ClojureScript?
● The Language
● The Ecosystem
Warning
Why ClojureScript?
Current status
● JavaScript is everywhere, but not a robust and concise
language - wat
Requires discipline to only use "the good parts"
● JavaScript is taking over: UI logic from server to client
● JavaScript is not going away in the near future
● Advanced libraries and technologies exist to optimize
JavaScript: (Google Closure, V8)
tl;dr:
● complexity is biggest problem in software
● mutability + control: more state, more complexity
● immutability + FP: less state, less complexity
Clojure(Script) promotes
source: http://www.drdobbs.com/architecture-and-design/the-clojure-philosophy/240150710
ClojureScript?
● Released June 20th 2011
● Client side story of Clojure ecosystem
● Serves Clojure community:
50%* of Clojure users also use ClojureScript
93%** of ClojureScript users also use Clojure
● ClojureScript targets JavaScript by adopting Google
Closure
○ libraries: goog.provide/require etc.
○ optimization: dead code removal
*http://cemerick.com/2013/11/18/results-of-the-2013-state-of-clojure-clojurescript-survey/
** http://blog.cognitect.com/blog/2014/10/24/analysis-of-the-state-of-clojure-and-clojurescript-survey-2014
The Language
Such parens...
f(x) -> (f x)
JavaScript - ClojureScript
no implementation (ns my.library
(:require [other.library :as other]))
var foo = "bar"; (def foo "bar")
// In JavaScript
// locals are mutable
function foo(x) {
x = "bar";
}
;; this will issue an error
(defn foo [x]
(set! x "bar"))
source: http://himera.herokuapp.com/synonym.html
JavaScript - ClojureScript
if (bugs.length > 0) {
return 'Not ready for release';
} else {
return 'Ready for release';
}
(if (pos? (count bugs))
"Not ready for release"
"Ready for release")
var foo = {bar: "baz"};
foo.bar = "baz";
foo["abc"] = 17;
(def foo (js-obj "bar" "baz"))
(set! (.-bar foo) "baz")
(aset foo "abc" 17)
source: http://himera.herokuapp.com/synonym.html
Core language features
● persistent immutable data structures
● functional programming
● sequence abstraction
● isolation of mutable state (atoms)
● Lisp: macros, REPL
● core.async
Persistent data structures
(def v (vector))
(def v [])
(def v [1 2 3])
(conj v 4) ;; => [1 2 3 4]
(get v 0) ;; => 1
(v 0) ;; => 1
source: http://hypirion.com/musings/understanding-persistent-vector-pt-1
Persistent data structures
(def m (hash-map))
(def m {})
(def m {:foo 1 :bar 2})
(conj m [:baz 3])
;; => {:foo 1 :bar 2 :baz 3}
(assoc m :foo 2) ;; => {:foo 2 :bar 2}
(get m :foo) ;;= > 2
(m :foo) ;;= > 2
(dissoc m :foo) ;;=> {:bar 2}
Functional programming
(def r (->>
(range 10) ;; (0 1 2 .. 9)
(filter odd?) ;; (1 3 5 7 9)
(map inc))) ;; (2 4 6 8 10)
;; r is (2 4 6 8 10)
Functional programming
;; r is (2 4 6 8 10)
(reductions + r)
;; => (2 6 12 20 30)
(reduce + r)
;; => 30
Sequence abstraction
Data structures as seqs
(first [1 2 3]) ;;=> 1
(rest [1 2 3]) ;;=> (2 3)
General seq functions: map, reduce, filter, ...
(distinct [1 1 2 3]) ;;=> (1 2 3)
(take 2 (range 10)) ;;=> (0 1)
See http://clojure.org/cheatsheet for more
Sequence abstraction
Most seq functions return lazy sequences:
(take 2 (map
(fn [n] (js/alert n) n)
(range))) infinite lazy sequence of numbers
side effect
Isolation of state
adapted from: https://github.com/dfuenzalida/todo-cljs
one of possible
pre-React
patterns
function called
from event
handler
(def app-state (atom []))
(declare rerender)
(add-watch app-state ::rerender
(fn [k a o n]
(rerender o n)))
(defn add-todo [text]
(let [tt (.trim text)]
(if (seq tt)
(swap! app-state conj
{:id (get-uuid)
:title tt
:completed false}))))
new todo
Lisp: macros
(map inc
(filter odd?
(range 10))))
(->>
(range 10)
(filter odd?)
(map inc))
thread last macro
Lisp: macros
(macroexpand
'(->> (range 10) (filter odd?)))
;; => (filter odd? (range 10))
(macroexpand
'(->> (range 10) (filter odd?) (map inc)))
;; => (map inc (filter odd? (range 10)))
Lisp: macros
JVM Clojure:
(defmacro defonce [x init]
`(when-not (exists? ~x)
(def ~x ~init)))
ClojureScript:
(defonce foo 1)
(defonce foo 2) ;; no effect
notes:
● macros must be written in JVM Clojure
● are expanded at compile time
● generated code gets executes in ClojureScript
LISP: Browser REPL (weasel)
core.async
(def ch (chan))
(go (loop []
(if-let [msg (<! ch)]
(do
(.log js/console msg)
(recur))
(println "terminating loop..."))))
(events/listen (by-id "button1") EventType.CLICK
#(put! ch "hello world!"))
(events/listen (by-id "button2") EventType.CLICK
#(close! ch))
The Ecosystem
Debugging
Source maps let you debug ClojureScript directly from the browser
Leiningen
● Used by 98% of Clojure users
● Clojure's Maven
● Managing dependencies
● Running a REPL
● Packaging and deploying
● Plugins:
○ lein cljsbuild
○ lein figwheel
(defproject example "0.1.0-SNAPSHOT"
:description "FIXME: write this!"
:url "http://example.com/FIXME"
:dependencies [[org.clojure/clojure "1.6.0"]
[org.clojure/clojurescript "0.0-2311"]]
:plugins [[lein-cljsbuild "1.0.4-SNAPSHOT"]]
:source-paths ["src"]
:cljsbuild {:builds [{:id "example"
:source-paths ["src"]
:compiler {
:output-to "example.js"
:output-dir "out"
:optimizations :none
:source-map true}}]})
figwheel: live code reloading
Editors
Most popular:
● Emacs
● Cursive Clojure
(IntelliJ)
● Vim + vim-fireplace
● Light Table
● Counterclockwise
(Eclipse)
cljs.core.typed
(ns foo)
(ann parse-int [string -> number])
(defn parse-int [s]
(js/parseInt s))
(parse-int 3)
Function foo/parse-int could not be
applied to arguments:
Domains:
string
Arguments:
(clojure.core.typed/Val 3)
Ranges:
number
in: (foo/parse-int 3)
ClojureScript interfaces to React
Talk today @ 17:40-18:20
(defn timer-component []
(let [seconds-elapsed (atom 0)]
(fn []
(js/setTimeout #(swap! seconds-elapsed inc) 1000)
[:div
"Seconds Elapsed: " @seconds-elapsed])))
Community
Google Groups
IRC: #clojure and #clojurescript on freenode
Planet Clojure
Reddit
Meetup.com
Interesting libraries
● cljs-http: ajax + core.async
● cljx: code sharing between clj and cljs
● clojurescript.test: unit testing
● dommy: dom manipulation
● crate: hiccup style HTML templating
● sente: websockets + core.async
● transit: conveying values between languages
● datascript: functional database in cljs
● garden: css generation from Clojure
Thank you!
https://github.com/borkdude/oredev2014
Trash can
June 20th 2011: first release of ClojureScript
Brief history of ClojureScript
Brief history of ClojureScript
Early 2012: first release of lein cljsbuild
Leiningen plugin to make ClojureScript development easy
98% of Clojure users use Leiningen
Possible optimization levels include
:whitespace removes comments and whitespace
:simple renames local variables to compress JavaScript
:advanced: agressively renames and strips away unused
Brief history of ClojureScript
April 2012:
persistent data structures were ported
Light Table
June 2012
Funded as Kickstarter Project
Interactive, experimental IDE written in
ClojureScript, running on Node Webkit
Became open source early 2014
Brief history of ClojureScript
October 2012: ClojureScript Up and Running - O'Reilly
Brief history of ClojureScript
August 2014
Transducers
(->>
(range 10)
(filter odd?)
(map inc))
(def xform
(comp
(filter odd?)
(map inc)))
;; lazy
(sequence xform
(range 10))
;; strict
(into [] xform (range 10))
core.async
transducers
EDN
ClojureScript
persistent data
structures
(immutable)
atoms (mutable)
Generated
optimized
JavaScript
Google
Closure
JavaScript
libraries
your program
Compiler
ClojureScript libs
Lisp: macros
(if (< x 5)
:foo
(if (> x 10)
:bar
:baz))
(cond (< x 5) :foo
(> x 10) :bar
:else :baz)
cond macro
Frameworks
● Pedestal
● Hoplon
● Luminus (curated collection of libs)

ClojureScript for the web