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 support
  • Bad tooling
  • Hard to debug
  • Nobody uses it
  • Only for academics
  • No learning resources
  • Not 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

 

 

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

  • 3,989