Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save paulkoegel/9722559 to your computer and use it in GitHub Desktop.
Save paulkoegel/9722559 to your computer and use it in GitHub Desktop.
ClojureScript - Lisp's Revenge (David Nolen.Goto conference)

LISP's Revenge - David Nolen (Goto Conference)

LISP

  • invented by John McCarthy in 1957 to solve the problem of artificial intelligence - simply because no existing programming language could meet his needs.
  • along the way, McCarthy invented interpreters, high-level meta programming, garbage collection, dynamic programming languages, functional programming, programming with recursive functions.
  • simplicity-to-expressive-power-ratio is VERY nice. Not the only language that has this, though.a

The languages we have today are not good tools for the task. We mainly build distributed systems on the web and nearly everything interesting we do is asynchronous - and with that also very likely concurrent.

ClojureScript

2007: Clojure released
2011: ClojureScript released - to target browsers, where the JVM can't run

precursors / similar: Google's Grid (Java to JavaScript), TypeScript (Microsoft), CoffeeScript, Dart. (related: JavaScript as Assembler of the web)

  • compatible with Google Closure compiler

  • the compiled JavaScript code of a "Hello world" program written in ClojureScript has 2 lines of code (~40 in Dart)

  • is it fast? yes: https://github.com/swannodette/chambered (only slightly slower than the JS implementation - "maybe 10%")

  • compile time: very fast

  • compiled JavaScript: not intended to be readable but fast - source maps to the rescue to still be able to debug normal source maps: only work for development, not production JS code. Source map only works for, e.g., CoffeeScript to JS compilation, after minification has happened the source map will no longer be valid. ClojureScript source maps: even work after minification and with production code.

  • 76 contributors, >2800 stars, 2nd most popular Leiningen plugin

  • Sierra, Stuart & VanderHart, Luke. 2012. ClojureScript: Up and Running. Functional Programming for the Web. O'Reilly.

Differences and Advantages over Vanilla JS

  • semantics: "try not to preserve JavaScript semantics unless we absolutely have to" - e.g. numerics - main goal is to preserve Clojure semantics instead. -> 0 and "" are not falsy (only nil and false are falsy in Clojure and the same's true for ClojureScript)

  • has proper lexical scoping

  • has no mutable locals solves common closure problem in JS where when running a loop you get the wrong value b/c the local was mutable. Closed-over variables don't behave like one would expect. in JS you'd end up with ten times the number ten, while in ClojureScript this code will return (0 1 2 3 4 5 6 7 8 9)

    (let [fns (atom [])
          invoke (fn [f] (f))]
      (dotimes [i 10]
        (swap! fns conj (fn [] i)))
      (map invoke @fns)
    )
    

    in JavaScript:

    var fns = [];
    var invoke = function(f) { f() };
    
    for(var i=0; i<10; i++) {
      fns.push(function(){console.log(i)});
    }
    
    for(var j=0; j < fns.length; j++) {
      invoke(fns[j]);
    }

    which will log "10" ten times :(

    See here for an illustrated example of JavaScript closures. Source code: https://gist.github.com/paulwittmann/9252076.

    ClojureScript has immutable local variables - when one of the functions in fns is invoked, it is passed an immutable local value - when the loop continues and increases its counter i, the functions inner local variable remains untouched - in JavaScript this is not the case. Only fns is a mutable array.

    JavaScript: strings and numbers don't change, rest is mutable.

Immutability

JavaScript: "All types except objects define immutable values. Specifically, strings are immutable (unlike in C for instance). We refer to values of these types as 'primitive values.'" (more: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures)

ClojureScript has immutable data types you have to explicitly specify what should be mutable - e.g. refs or atoms.

```clojurescript
(let [x {:foo "bar"}]
  (println)
  (println (assoc x :baz "woz"))
  (println x))
)
```

prints:

```clojurescript
{:baz woz, :foo bar}
{:foo bar}
```

and returns:

```clojurescript
nil
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment