Clojure/ClojureScript: One Language to Rule the Web

by Stuart Sierra
JavaOne 2012
San Francisco, CA
September 28, 2012

Table of Contents

Clojure/ClojureScript    titleslide slide

One Language to Rule the Web

@stuartsierra

clojure.com

What is Clojure?    slide

Namespace declaration and function definition:

(ns javaone.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 javaone.examples)

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

Compare to Java:

package javaone.examples;

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

What is ClojureScript?    slide

Why?    topic1 slide

Why JavaScript?    topic1 slide

JS is the Assembly Language of the Web    notitle slide

Always Bet on JavaScript    notitle slide

alwaysbetonjs.com (Brendan Eich)

But It's JavaScript!    slide

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 :quuz 96)
;;=> {:foo 41, :bar 42, :quuz 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]" (slurp "hamlet.txt")))
;;=> {"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 "JavaOne")  ;=> "Hello, JavaOne!"

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

Namespaces    slide

(ns javaone.examples.foo)

(defn greet []
  (println "Hello, JavaOne!"))
(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)))

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 javaone.examples/my-project "0.0.1-SNAPSHOT"
  :plugins [[lein-cljsbuild "0.2.7"]]
  :cljsbuild {:builds
                [{:source-path "src"
                  :compiler
                    {:output-to "my-project.js"
                     :optimizations :advanced
                     :pretty-print false}}]})

Why Both?    slide

Share Data    slide

  • pr-str and read on both client & server
  • Extensible Data Notation (edn)
    • Literal syntax of Clojure(Script)
    • Also implemented in Ruby 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!

Share Code    slide

  • Code without host interop just works
  • lein-cljsbuild has "crossovers"
  • Feature expressions coming to Clojure

The End    notitle slide

Photo credits: morgueFile and Stefanie Watson