ClojureScript
http://clojure.org/
https://github.com/clojure/clojurescript
Valentin Waeselynck
@val_waeselynck
Clojure
- designed to combat complexity
- released in 2007 for the JVM
- targets JavaScript since 2011 ('ClojureScript')
What is Clojure ?
- Functional, dynamic
- modern LISP (not Common Lisp or Scheme)
- Data-oriented (Sequences, maps)
- Simple
- Pragmatic
- Close to the metal
Objectives: simplicity, power, focus.
Features
- dynamically typed
- first-class functions
- immutable data structures
- namespaces
- macros
- REPL and hot-code loading
- good JS interop
- advanced: polymorphism constructs
ClojureScript
- generates JavaScript code (ES3)
- on all platforms
- including Node, Nashorn
- optimized for advanced minification
- ships with a REPL
Fizzbuzz
(doseq [msg (for [i (range 100)]
(let [mult-of-3 (= (mod i 3) 0)
mult-of-5 (= (mod i 5) 0)]
(cond
(and mult-of-3 mult-of-5) "fizzbuzz"
mult-of-3 "fizz"
mult-of-5 "buzz"
:else i)
))]
(println msg))
Why use ClojureScript?
- very expressive
- simple, natural
- interactive development
- high quality ecosystem
- focus
MUST-WATCH
Syntax
JavaScript
Clojure
f(x);
(f x)
f(x, y, z);
(f x y z)
Demo
Immutable data structures
- can't be changed
- just like numbers, strings and booleans in JS
- are composite values
- more natural support for information
- made possible by recent algorithmic research
- Recently adopted in the JS ecosystem (React)
Immutable data structures
var Immutable = require('immutable');
var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 12);
print(map1); // => {'a': 1, 'b': 2, 'c': 3}
print(map1); // => {'a': 1, 'b': 12, 'c': 3}
var list1 = Immutable.List.of('a','b','c');
var list2 = list1.push('d');
print(list1); // => ['a', 'b', 'c']
print(list2); // => ['a', 'b', 'c', 'd']
(def map1 {:a 1 :b 2 :c 3})
(def map2 (assoc map1 :b 12))
(def vec1 ["a" "b" "c"])
(def vec2 (conj vec1 "d"))
Holding state: atoms
var Immutable = require('immutable');
var todosState = new Atom(Immutable.List.of("laundry", "grocery"));
function addTodo(newTodo){
todosState.swap(function(todos){
return todos.conj(newTodo);
});
}
function getTodos(){
return todosState.deref();
}
(def todos-state (atom ["grocery" "laundry"]))
(defn get-todos [] @todos-state)
(defn add-todo! [new-todo]
(swap! todos-state (fn [todos] (conj todos new-todo))
))
Macros
How macros work
- operate at compile-time
- 'rewrite' the code that is passed to them
- are defined as functions that accept code and return code
Example: doto
(doto (new js/XMLHttpRequest)
(.open "GET" "ajax_info.txt" true)
(.send))
(let [x (new js/XMLHttpRequest)]
(.open x "GET" "ajax_info.txt" true)
(.send x)
x)
is as if I had written
var x = new XMLHttpRequest();
x.open("GET","ajax_info.txt",true);
x.send();
return x;
in JS:
our macro
Macros: benefits
- syntax is never in the way
- code made as concise as possible
- trivially
- separation of concerns (syntax | program structure)
- 'add language features'
- pattern-matching, Golang-style CSP, logic, ...
- 'A language should be planned for growth' (Guy Steele)
- dev tooling
Macros and Functions
FUNCTIONS
- manipulate values
- at run-time
- calling a function -> executing its body
- factor out execution
MACROS
- manipulate code
- at compile-time
- calling a macro -> writing its body
- factor out code
Application: pattern matching
(ns foo.bar
(:require [cljs.core.match :refer-macros [match]]))
(doseq [n (range 100)]
(println
(match [(mod n 3) (mod n 5)]
[0 0] "fizzbuzz"
[0 _] "fizz"
[_ 0] "buzz"
:else n)))
Application: Golang-style CSPs
(ns foo.bar
(:require [cljs.core.async :refer [<! >!] :refer-macros [go]]))
(defn load-stories! []
(go (try
(let [story (<! (get-json "story.json"))
{:keys [story chapterURLS]} story
=chapters= (map get-json chapterURLS)]
(doseq [=chapter= =chapters=]
(add-html-to-page! (<! =chapter=)))
(add-text-to-page! "All done"))
(catch js/Error err
(add-text-to-page! (str "Argh, broken: " (:message err))))
)))
async operations
Google Closure
- JS -> JS compiler
- JavaScript library
- Foundation for ClojureScript
Google Closure: the compiler
- advanced minification
- including Dead Code Elimination
- you only pay for the code you use !
- solves the 'library problem'
Google Closure: the library
- very comprehensive
- especially for cross-browser
- battle-tested (GMail, Google Docs, etc.)
Google Closure: downsides
- unpleasant to use from JS
- verbose
- Java-like
- requires discipline
- ClojureScript makes it practical!
React
ClojureScript is ready
- the language has reached stability
- simple, tiny core => easier to get right
- grows by libraries
- incl. tooling (editors, debuggers, REPL etc.)
- carefully designed
- Google Closure: solid foundation
- integrates with the JS ecosystem
Community
- friendly :)
- professional
- innovative
- wise
Problems with ClojureScript
- No editor support
- Bad tooling
- Hard to debug
- Nobody uses it
- Only for academics
- No learning resources
- Not for the real world
No editor supportBad toolingHard to debugNobody uses itOnly for academicsNo learning resourcesNot for the real world- Unfamiliar
Do NOT use ClojureScript
- For Hello World
- As a first language
- (the 2nd language will hurt)
Where JavaScript comes from
I was recruited to Netscape with the promise of “doing Scheme” in the browser.
- Brendan Eich
JavaScript is LISP in C's clothing.
- Douglas Crockford
Where JavaScript is going
- Managing state and asynchrony is hard
- Functional is the way to go
- React, Immutable.js (Facebook), Redux
- For UIs, interactive development is mandatory
- livereload, build tools, browserify, react-hot-loader
- We're not satisfied with the adopted standards
- Babel, TypeScript, ATScript
ClojureScript is already there
clojure.core
JS interop
Google Closure
λ
state management
(atoms)
syntax extension
(macros)
Big picture
Questions?
Valentin Waeselynck
@val_waeselynck
Useful links
- Github
- Cheatsheet
- online REPL
- translations from JS
- IDE
- clojure site
- Leiningen (build tool)
- Figwheel (hot-code loading)
- 4clojure
- devcards
- everything from David Nolen
Clojure crash course
Data types - scalars
- Numbers
- Booleans
- Strings
- Null
- Keywords
- Symbols
42 3.14 -1.2e3 0x0000ff 32r3J2V
true false
"hello world"
"multi
line
string"
nil
:a :key :foo :bar :namespaced.qualified/key ::nq/key
john doe if cond hello-world <tag> even?
Data types - collections
- Lists: singly linked, grow at front
- Vectors: indexed access, grow at end
- Maps: key-value pairs
- Sets: duplicate-free
(1, 2, 3, 4) ("a" :b c 4 false) (list 1 2 3)
[1 2 3 4] ["a" :b c 4 false] [list 1 2 3]
{:a 1, "b" 2,[:x :y] 3} {:class "fa fa-email" :style "display: none;"}
#{2 3 5 7 11 13}
Naming things
(ns my.module
; other module
(:require [other.namespace :as other]))
(def x 42) ; global variable
(def y (+ x other/a))
(def z
(let [u (* x 3)] ; local variable
(- u y))
var other = require("other/namespace");
var x = 42;
var y = x + other.a;
var u = x * 3;
var z = u - on.y;
module.exports = {
x: x,
y: y,
z: z
};
Functions
(ns my.module)
(def add1 (fn [x] (+ x 1)))
; shorthand for the above
(defn add2 [x]
(+ x 2))
(def add3 #(+ % 3))
; calling the function
(add1 41)
var add1 = function(x){
return x + 1;
}
var add3 = x => x + 3;
// calling the function
add1(41);
module.exports = {
add1: add1,
add3: add3
};
Control flow
; low-level `if`
(def greeting
(if (= lang :fr)
"Bonjour tout le monde"
"Hello World"))
; higher-level macros
(def greeting2
(cond
(= lang :en) "How are you?"
(and (= lang :fr) several)
"Comment allez-vous ?"
(and (= lang :fr) (not several))
"Comment vas-tu ?"
:je-galere "Hum, do you speak English?"
))
(def greeting3
(case lang
:fr "Bonjour tout le monde"
:de "Guten Tag"
"Hello World"))
;; NOTE: everything is an expression.
var greeting, greeting2, greeting3;
if(lang === 'fr'){
greeting = "Bonjour tout le monde";
} else {
greeting = "Hello world";
}
if(lang === 'en'){
greeting2 = "How are you?";
} else if(lang === 'fr' && several){
greeting2 = "Comment allez-vous ?";
} else if(lang === 'fr' && !several){
greeting2 = "Comment vas-tu ?";
} else {
greeting2 = "Hum, do you speak English?";
}
switch(lang){
case 'fr':
greeting3 = "Bonjour tout le monde"
break;
// [...]
}
JavaScript interop
(js/alert "coucou")
; access property
(.-innerHtml el)
(aget el "innerHtml")
; set property
(set! (.-innerHtml el) "Coucou")
(aset el "innerHtlm" "Coucou")
; call method
(.log js/console "Coucou")
; create native object
(js-obj "a" 1 "b" 2)
#js {:a 1 :b 2}
; create native array
(array "a" "b" "c")
#js ["a" "b" "c"]
; accessing nested properties is a bit awkward...
(.c (-.b (-.a myObj)) x y)
; ... luckily, we can use a macro!
(-> myObj .-a .-b (.c x y))
alert("coucou")
el.innerHtml
el["innerHtml"]
el.innerHtml = "Coucou";
el["innerHtml"] = "Coucou";
console.log("Coucou")
{a: 1, b: 2}
["a", "b", "c"]
myObj.a.b.c(x,y)
Collections: sequences
(conj [1 2 3 4 5] 6)
=> [1 2 3 4 5 6]
(map #(* % 2) [1 2 3 4 5])
=> (2 4 6 8 10)
(filter even? [1 2 3 4 5])
=> (2 4)
(reduce + [1 2 3 4 5])
=> 15
(drop 2 [1 2 3 4 5])
=> (3 4 5)
(interleave [:a :b :c :d :e] [1 2 3 4 5])
=> (:a 1 :b 2 :c 3 :d 4 :e 5)
(partition 3 [1 2 3 4 5 6 7 8 9])
=> ((1 2 3) (4 5 6) (7 8 9))
;; using the ->> ('thread-last') macro for chaining
(->> [0 1]
(iterate (fn [[x y]] [y (+ x y)]))
(map first)
(take 12))
=> (0 1 1 2 3 5 8 13 21 34 55 89)
Collections: maps
(def m {:a 1 :b 2 :c 3})
(get m :b)
=> 2
; maps are functions of their keys
(m :b)
=> 2
; keywords are function of the maps
(:b m)
=> 2
(assoc m :d 4)
=> {:a 1, :b 2, :c 3, :d 4}
(dissoc m :a )
=> {:a 1 :c 3}
(merge-with + m {:a 2 :b 3})
=> {:a 3, :b 5, :c 3}
(def m2 {:a 1 :b {:c 2 :d 3}})
(get-in m2 [:b :c])
=> 2
(assoc-in m2 [:b :c] 12)
=> {:a 1, :b {:c 12, :d 3}}
(update-in m2 [:b :c] #(* % 3))
=> {:a 1, :b {:c 6, :d 3}}
There's more...
- destructuring
- polymorphism:
- multimethods
- protocols
ClojureScript Paris.js
By Val Waeselynck
ClojureScript Paris.js
- 4,159