Clojure/ClojureScript: One Language to Rule the Web

by Stuart Sierra
Emerging Technologies for the Enterprise 2013
Philadelphia, PA
April 3, 2013

Table of Contents

Related talks at Philly E.T.E. 2013

Clojure/ClojureScript    titleslide slide

What is Clojure?    slide

Namespace declaration and function definition:

(ns phillyete.examples)

(defn average [& nums]
  (/ (reduce + nums)
     (count nums)))

Function invocation:

(average 4 11)
;;=> 15/2

(average 3.0 72 9.6 33)
;;=> 29.4

Compare with Java    notitle slide

(ns phillyete.examples)

(defn average [& nums]
  (/ (reduce + nums)
     (count nums)))

Compare to Java:

package phillyete.examples;

public class Averages {
    public static double average(double[] nums) {
        double total = 0.0;
        for (double num : nums) {
            total += num;
        }
        return total / nums.length;
    }
}

Why?    topic1 slide

JS is the Assembly Language of the Web    notitle slide

Always Bet on JavaScript    notitle slide

alwaysbetonjs.com (Brendan Eich)

Why Clojure?    topic1 slide

Immutable Data Structures    slide

(print "hello" "world")    ; List

[1 "bob" 3 :alice]         ; Vector

{:b 2, [:any "keys"] 42}   ; Map

#{3 7 22 :bob}             ; Set

Immutable Data Structures    slide

(def m {:foo 41, :bar 42})

(assoc m :quux 96)
;;=> {:foo 41, :bar 42, :quux 96}

m  ;=> {:foo 41, :bar 42}

Data Manipulation    slide

Sum of the first 100 even numbers:

(reduce + (take 100 (filter even? (range))))
;;=> 9900

Frequency of vowels in Hamlet:

(frequencies (re-seq #"[aeiou]" hamlet))
;;=> {"o" 11557, "e" 15819, "u" 4542, "a" 9857, "i" 8218}

Multi-Arity Functions    slide

(defn greet
  ([]
     (greet "Hello" "World"))
  ([name]
     (greet "Hello" name))
  ([greeting name]
     (str greeting ", " name "!")))
(greet)  ;=> "Hello, World!"

(greet "Philly E.T.E.!")  ;=> "Hello, Philly E.T.E.!"

(greet "Good afternoon" "citizens")
;;=> "Good afternoon, citizens!"

Namespaces    slide

(ns phillyete.examples.foo)

(defn greet []
  (println "Hello, Philly E.T.E.!"))
(ns com.example.app)

(defn greet []
  (println "Hello, World!"))

Protocols    slide

(ns cljs.core)

(defprotocol ICounted
  (count [coll] "Constant-time count"))

(deftype ArrayChunk [arr off end]
  ICounted
  (count [_] (- end off)))

(extend-type array    ; JavaScript built-in array
  ICounted
  (count [a] (alength a)))

Protocols in Domina    slide

(ns domina)

(defprotocol DomContent
  (nodes [content] "Returns seq of nodes")
  (single-node [nodeseq] "Returns a single node"))

(extend-protocol DomContent
  string
  (nodes [s] (doall (nodes (string-to-dom s))))
  (single-node [s] (single-node (string-to-dom s)))

  js/NodeList
  (nodes [nl] #_...)
  (single-node [nl] (. content (item 0))))

Time & State    slide

(def scores (atom {:lee 49, :terry 50}))

@scores  ;=> {:lee 49, :terry 50}


(swap! scores update-in [:lee] + 7)

@scores  ;=> {:lee 56, :terry 50}

Why ClojureScript?    topic1 slide

Compact    slide

Google Closure Compiler    slide

Original source:

function doStuff(alpha, beta) {
    var sum = alpha + beta;
    alert("The sum of " + alpha + " and " +
          beta + " is " + sum);
}

doStuff(7, 5);

Whitespace-Only optimization mode:

function doStuff(alpha,beta)
{var sum=alpha+beta;
alert("The sum of "+alpha+" and "+beta+" is "+sum)}
doStuff(7,5);

Google Closure Compiler    slide

Original source:

function doStuff(alpha, beta) {
    var sum = alpha + beta;
    alert("The sum of " + alpha + " and " +
          beta + " is " + sum);
}

doStuff(7, 5);

Simple optimization mode:

function doStuff(a,b)
{alert("The sum of "+a+" and "+b+" is "+(a+b))}
doStuff(7,5);

Google Closure Compiler    slide

Original source:

function doStuff(alpha, beta) {
    var sum = alpha + beta;
    alert("The sum of " + alpha + " and " +
          beta + " is " + sum);
}

doStuff(7, 5);

Advanced optimization mode:

alert("The sum of 7 and 5 is 12");

Fast    slide

JavaScript Spectral Norm    slide

JavaScript code by Ian Osgood and Roy Williams:

function A(i,j) {
  return 1/(((i+j)*(i+j+1)>>>1)+i+1);
}

function Au(u,v) {
  var n = u.length;
  for (var i=0; i<n; ++i) {
    var t = 0;
    for (var j=0; j<n; ++j)
      t += A(i,j) * u[j];
    v[i] = t;
  }
}

function Atu(u,v) {
  var n = u.length;
  for (var i=0; i<n; ++i) {
    var t = 0;
    for (var j=0; j<n; ++j)
      t += A(j,i) * u[j];
    v[i] = t;
  }
}

function AtAu(u,v,w) {
  Au(u,w);
  Atu(w,v);
}

function spectralnorm(n) {
  var storage_ = new ArrayBuffer(n * 24);
  var u = new Float64Array(storage_, 0, n),
      v = new Float64Array(storage_, 8*n, n),
      w = new Float64Array(storage_, 16*n, n);
  var i, vv=0, vBv=0;
  for (i=0; i<n; ++i) {
    u[i] = 1; v[i] = w[i] = 0; 
  }
  for (i=0; i<10; ++i) {
    AtAu(u,v,w);
    AtAu(v,u,w);
  }
  for (i=0; i<n; ++i) {
    vBv += u[i]*v[i];
    vv  += v[i]*v[i];
  }
  return Math.sqrt(vBv/vv);
}

print(spectralnorm(arguments[0]).toFixed(9));

/* COMMAND LINE:
/usr/local/src/v8/out/native/d8 --nodebugger spectralnorm.v8-3.v8 -- 5500

PROGRAM OUTPUT:
1.274224153 */

ClojureScript Spectral Norm    slide

Adapted from ClojureScript code by David Nolen

(ns spectral-norm.core)

(defn a [i j]
  (/ 1 (+ (/ (* (+ i j) (+ i j 1)) 2) i 1)))

(defn au [n u v]
  (dotimes [i (.-length u)]
    (loop [t 0
           j 0]
      (if (< j n)
        (recur (+ t (* (a i j) (aget u j)))
               (inc j))
        (aset v i t)))))

(defn atu [n u v]
  (dotimes [i (.-length u)]
    (loop [t 0
           j 0]
      (if (< j n)
        (recur (+ t (* (a j i) (aget u j)))
               (inc j))
        (aset v i t)))))

(defn at-au [n u v w]
  (au n u w)
  (atu n w v))

(defn spectralnorm [n]
  (let [storage (js/ArrayBuffer. (* n 24))
        u (js/Float64Array. storage 0 n)
        v (js/Float64Array. storage (* n 8) n)
        w (js/Float64Array. storage (* n 16) n)]
    (dotimes [i n] (aset u i 1) (aset v i 0) (aset w i 0))
    (dotimes [i 10]
      (at-au n u v w)
      (at-au n v u w))
    (loop [i 0 vbv 0 vv 0]
      (if (< i n)
        (recur (inc i)
               (+ vbv (* (aget u i) (aget v i)))
               (+ vv (* (aget v i) (aget v i))))
        (Math/sqrt (/ vbv vv))))))

(set! *print-fn* js/print)

(print (spectralnorm 5500))

Still JavaScript    slide

Raphaël Demo    slide

var paper = Raphael("canvas", 640, 480),
    btn = document.getElementById("run");

(btn.onclick = function () {
    paper.clear();
    paper.rect(0, 0, 640, 480, 10).
          attr({fill: "#fff", stroke: "none"});
    paper.circle(320, 240, 60).
          animate({fill: "#223fa3", stroke: "#000",
                   "stroke-width": 80, "stroke-opacity": 0.5},
                  2000);
})();

Adapted from the Raphael.js Playground demo

Raphaël in ClojureScript    slide

(ns raphael-demo)

(def paper (js/Raphael. "canvas" 640 480))
(def button (.getElementById js/document "run"))

(set! (.-onclick button)
      #(doto paper
         .clear
         (-> (.rect 0 0 640 480 10)
             (.attr (js-obj "fill" "#fff", "stroke" "none")))
         (-> (.circle 320 240 60)
             (.animate (js-obj "fill" "#223fa3", "stroke" "#000",
                               "stroke-width" 80, "stroke-opacity" 0.5)
                       2000))))

Challenges    slide

Debugging & Profiling    slide

Tooling    slide

lein-cljsbuild    slide

project.clj:

(defproject phillyete.examples/my-project "0.1.0-SNAPSHOT"
  :plugins [[lein-cljsbuild "0.3.0"]]
  :cljsbuild {:builds
                [{:source-paths ["src/cljs"]
                  :compiler
                    {:output-to "resources/development.js"
                     :output-dir "resources/js"
                     :optimizations :none}}
                 {:source-paths ["src/cljs"]
                  :compiler
                    {:output-to "production.js"
                     :optimizations :advanced}}]})

bREPL Demo    slide

(ns example.main
  (:require [domina :as d]
            [clojure.browser.repl :as repl]))

(defn greet [name]
  (d/append! (d/by-id "content")
             (str "<h1>Hello, " name "!</h1>")))

(defn start []
  (repl/connect "http://localhost:9000/repl"))

Why Both?    slide

Share Data    slide

  • Extensible Data Notation (edn)
  • Literal syntax of Clojure(Script)
  • clojure.edn in Clojure
  • cljs.reader in ClojureScript
  • Also implemented in Ruby, .NET, and other languages

Tagged Literals    slide

#inst "2012-09-30T14:10:04.817-00:00"
  • Read in JVM as java.util.Date
  • Read in JavaScript as Date
  • Define your own!

EDN versus JSON    slide

{:user/id    9987
 :user/name  "Stuart Sierra"     ; Namespaced keys
 :group/name ["Clojure"]
 :view/mode  'classic            ; Symbols & keywords
 :join-date  #inst "2012-01-03"  ; Tagged literals
 [:option 1] "foo"               ; Non-string keys
 :friends    #{1234 3347}        ; Unordered sets
 :priv       #priv {:type :user  ; User-defined tags
                    :admin true}}

Share Code    slide

  • Code without host interop just works
  • lein-cljsbuild has "crossovers"
  • Feature expressions coming to Clojure
  • Test in Clojure, deploy in ClojureScript!

Pedestal    slide

pedestal.io (Relevance)

The End    notitle slide

Photo credits: morgueFile and Stefanie Watson