<>Clojure is Awesome!

Clojure is Awesome!

A sampling of things that make Clojure awesome

Mark Bastian

markbastian@gmail.com

3/24/2015

What is Clojure?

  • A functional language hosted on the JVM
  • A practical Lisp

Clojure Collections Are Awesome!

Java Collections

Clojure Collections Representation

  • Clojure collections have a very simple representation
  • This makes them very easy to read and work with

Clojure Collections are Java Collections

package interop;

import clojure.lang.RT;

import java.util.List;
import java.util.Map;
import java.util.RandomAccess;
import java.util.Set;

public class Interop {
    public static void main(String[] args){
        Map<String, Integer> m = (Map<String, Integer>)RT.readString("{ \"abc\" 123 }");
        System.out.println(m.get("abc"));
        Set<String> s = (Set<String>)RT.readString("#{ \"abc\" \"123\" }");
        System.out.println(s.contains("abc"));
        List<String> v = (List<String>)RT.readString("[ \"abc\" ]");
        System.out.println(v.get(0));
        System.out.println(v instanceof RandomAccess);
    }
}

Note: Read Only

Syntax: Collection Initialization

Java

final Set<String> names = new HashSet<String>();
final List<Integer> ages = new LinkedList<Integer>();
final Map<String, Integer> names2ages = new HashMap<String, Integer>();

Clojure

(def names #{})
(def ages [])
(def names->ages {})

Java Collection Initialization

final Set<String> names = new HashSet<String>();
names.add("Curly");
names.add("Larry");
names.add("Moe");

final List<Integer> ages = new LinkedList<Integer>();
ages.add(20);
ages.add(21);
ages.add(22);

final Map<String, Integer> names2ages = new HashMap<String, Integer>();

//This is inefficient but preserves our list
int i = 0;
for(String name : names){
    names2ages.put(name, ages.get(i));
    i++;
}

//This is efficient since we are popping, but the list is mutated :(
for(String name : names){
    names2ages.put(name, ages.remove(0));
}

Clojure Collection Initialization

Clojure

(def names #{ "Curly" "Larry" "Moe"})
(def ages [20 21 22])
(def names->ages (zipmap names ages))
  • Inlining like this is called a "collection literal"
  • Scala also has collection literals
  • Sorry, Java

Clojure Collections API

  • Clojure collections have a unified API
  • Common Clojure data structures are:
    • Nested: It is common for collections to be collections of collections
    • Functions: maps, sets, and vectors are functions
    • Heterogeneous: Collection elements can be of any type
  • Note: Lists are different
    • They are rarely used to represent data
    • They are used to represent code

Unified Interface for Updates

Clojure

(conj [1 2 3] 4) ;Vector insertion
=> [1 2 3 4] ;Faster to add at the end

(conj #{1 2 3} 4) ;Set insertion
=> #{1 4 3 2}

(conj {"ONE" 1 "TWO" 2 "THREE" 3} ["FOUR" 4]) ;Map insertion
=> {"FOUR" 4, "THREE" 3, "TWO" 2, "ONE" 1}

(conj '(1 2 3) 4) ;List insertion
=> (4 1 2 3) ;Faster to add at the front

One function for "efficient" collection addition

Unified Interface for Gets

Clojure

(get [1 2 3 4] 2) ;Get the item at index 2
=> 3

(get #{1 2 3 4} 2) ;Get 2 from the set
=> 2

(get {"ONE" 1 "TWO" 2 "THREE" 3 "FOUR" 4} "TWO") ;Get "TWO" from the map
=> 2

Same interface for any random access data structure

Unified Interface for Failure

Clojure

(get [1 2 3 4] 5)
=> nil

(get #{1 2 3 4} 5)
=> nil

(get {"ONE" 1 "TWO" 2 "THREE" 3 "FOUR" 4} "FIVE")
=> nil
  • If something is missing, return nil
  • Clojure nil is Java null
  • No NPEs!

Unified Interface for Defaults

Clojure

(get [1 2 3 4] 5 5)
=> 5

(get #{1 2 3 4} 5 5)
=> 5

(get {"ONE" 1 "TWO" 2 "THREE" 3 "FOUR" 4} "FIVE" 5)
=> 5

Get takes an optional final argument to return when the item is missing from the collection

Nested Heterogeneous Data

Clojure

;Nested & Heterogeneous
(def data { :a 1 :b [1 2 3 4 5] :c { :name "Bob" :age 19 }})
=> #'user/data
(get-in data [:c :name]) ;I can drill down
=> "Bob"
(get-in data [:foo :baz :wonk]) ;Bad paths just return nil
=> nil
(get-in data [:c "shoe size"] 14) ;I can provide defaults
=> 14
  • I can flexibly define my data any way I want
  • Complex access is trivial using get-in
  • No NPEs!
  • I can provide defaults when data is missing

Nested Heterogeneous Data: Updating

Clojure

;Nested & Heterogeneous
(def data { :a 1 :b [1 2 3 4 5] :c { :name "Bob" :age 19 }})
=> #'user/data

(update-in data [:c :age] inc) ;Update value via a function
=> {:c {:age 20, :name "Bob"}, :b [1 2 3 4 5], :a 1}

(assoc-in data [:c "shoe size"] 14) ;insert or replace
=> {:c {:age 19, :name "Bob", "shoe size" 14}, :b [1 2 3 4 5], :a 1}
  • I can update complex data structures using a unified API
  • assoc = associate: Add or replace a value
  • update: Update a value via a function
  • Provided path works for any random access data structure

Nested Heterogeneous Data: Serialization

Clojure

(require 'clojure.edn)
=> nil

(def data { :a 1 :b [1 2 3 4 5] :c { :name "Bob" :age 19 }})
=> #'user/data

(spit "my.edn" data)
=> nil

(def reconstituted (clojure.edn/read-string (slurp "my.edn")))
=> #'user/reconstituted

reconstituted
=> {:c {:age 19, :name "Bob"}, :b [1 2 3 4 5], :a 1}
  • Clojure data and code are represented identically (homoiconicity)
  • A simple consequence of this is that serialization is trival
  • Note: clojure.edn/read-string won't execute code

In Conclusion:

Clojure Collections are Awesome!

  • There's more; For example:
    • Clojure collections are Java read-only collections
    • There are tons of functions for using collections
  • But I can't cover everything here

Clojure Targets Are Awesome!

Write Code Once

Wouldn't it be cool if...

  • You could write code that runs on the JVM?
  • And run as JavaScript in the browser?
  • That would be awesome!

Clojure, ClojureScript, CLJX

  • Clojure is a hosted Lisp on the JVM
  • ClojureScript is a hosted Lisp that compiles to JavaScript
  • CLJX is a Leiningen plugin that allows you to write common code to compile to both targets

In Conclusion:

Clojure Compilation Targets are Awesome!

  • Why write everything twice (or more)
  • Do it in Clojure!

Clojure Homoiconicity is Awesome!

Homoiconicity

What is Homoiconicity?

  • Homoiconic code is represented in its own data structures
  • Is the following code or data?
(def code-or-data? { :name "Bob" :age 40 :shoe-size 10 })
=> #'user/code-or-data?

(prn code-or-data?)
{:age 40, :shoe-size 10, :name "Bob"}
=> nil

(+ 2 3 4 5 6)
=> 20

'(+ 2 3 4 5 6)
=> (+ 2 3 4 5 6)

Code as Data: Macros

  • What do you do with data?
    • Manipulate, transform, analyze
  • If code is data, I can...
    • Manipulate, transform, and analyze it
    • This is what macros are all about

A Simple Macro

  • Tired of printlns everywhere?
  • Check this out...
(defmacro ? "Print a form and its value"
  [& rest]
  `(doto ~rest (->> (str '~rest " -> ") prn)))
=> #'user/?

;This form is pretty simple, but what if you wanted to know what it did?
(+ 1 2 3 4 5 6 7)
=> 28

;You could do this, but it is ugly and you have to pull out the form later.
(prn (+ 1 2 3 4 5 6 7))
28
=> nil

;Just stick the macro in there and see...
(? + 1 2 3 4 5 6 7)
"(+ 1 2 3 4 5 6 7) -> 28"
=> 28

Code as Data: Data Formats

  • What else do you do with data?
    • Manipulate, transform, analyze
    • Serialize, deserialize
    • This is what XML and JSON are all about
    • HTML documents are also data
  • If code is data, I can...
    • Easily convert my code to a different data or document format

Clojure is JSON

Clojure

(require '(clojure.data [json :as json]))
=> nil
(json/write-str {:a 1 :b [1 2 3 4 5]})
=> "{\"b\":[1,2,3,4,5],\"a\":1}"
(json/read-str "{\"a\":1,\"b\":[1,2,3,4]}" :key-fn keyword)
=> {:a 1, :b [1 2 3 4]}
https://github.com/clojure/data.json

Clojure is HTML

Clojure

(require 'hiccup.core)
=> nil
(def slide
  [:section
     [:h2 "Wouldn't it be cool if..."]
     [:ul
      [:li "You could write code that runs on the JVM?"]
      [:li "And run as JavaScript in the browser?"]
      [:li "And transform itself to and from HTML?"]
      [:li "And transform itself to and from JSON?"]
      [:li "That would be awesome!"]]])
=> #'user/slide
(def html-text (hiccup.core/html slide))
=> #'user/html-text
html-text
=> "<section><h2>Wouldn't it be cool if...<ul><li>You could write code that runs on the JVM?</li><li>And run as JavaScript in the browser?</li><li>And transform itself to and from HTML?</li><li>And transform itself to and from JSON?</li><li>That would be awesome!</li></ul></h2></section>"
(require '[hiccup-bridge.core :as hicv])
=> nil
(def recovered (hicv/html->hiccup html-text))
=> #'user/recovered
recovered
=> ([:section [:h2 "Wouldn't it be cool if..."] [:ul [:li "You could write code that runs on the JVM?"] [:li "And run as JavaScript in the browser?"] [:li "And transform itself to and from HTML?"] [:li "And transform itself to and from JSON?"] [:li "That would be awesome!"]]])
(= slide (first recovered))
=> true

In Conclusion:

Clojure Homoiconicity is Awesome!

  • With Homoiconicity:
    • Code is data; data is code
    • It's easy to transform data (hence code)
  • What would it take to do this in Java?

Clojure Concurrency is Awesome!

Java Concurrency

Java Concurrency

  • java.util.concurrent has DOZENS of classes for concurrency
  • java.util.concurrent.atomic
  • java.util.concurrent.locks
  • How do I understand this?
    • JCIP - 384 pages
    • Are you kidding me?
    • http://docs.oracle.com/cd/A97688_16/generic.903/bp/java.htm
  • So not awesome!

Clojure Concurrency

  • Everything is immutable (implicity thread safe)
  • 3 Primitives
    • agents: Asynchronous & Uncoordinated
    • atoms: Synchronous & Uncoordinated
    • ref: Synchronous & Coordinated
  • This is awesome!

In Conclusion:

Clojure Concurrency Awesome!

  • Java concurrency is NOT awesome
  • Immutability gives concurrency
  • Clojure concurrency primitives are easy to use

Clojure REPLs are Awesome!

What is a REPL?

  • Read-Evaluate-Print-Loop
  • An interactive shell session

The Lein Repl

  • lein repl
  • Puts you in a repl for your project
  • All your files are on your classpath

Awesome REPL Commands

  • The REPL has all kinds of cool interactive commands
  • doc: Print documentation
  • source: Prints the source
  • find-doc: Find documentation with a string
  • javadoc: Load javadoc in a browser
  • and more...

lein: Doc

(doc and)
-------------------------
clojure.core/and
([] [x] [x & next])
Macro
  Evaluates exprs one at a time, from left to right. If a form
  returns logical false (nil or false), and returns that value and
  doesn't evaluate any of the other expressions, otherwise it returns
  the value of the last expr. (and) returns true.
=> nil

lein: source

(source let)
(defmacro let
  "binding => binding-form init-expr

  Evaluates the exprs in a lexical context in which the symbols in
  the binding-forms are bound to their respective init-exprs or parts
  therein."
  {:added "1.0", :special-form true, :forms '[(let [bindings*] exprs*)]}
  [bindings & body]
  (assert-args
     (vector? bindings) "a vector for its binding"
     (even? (count bindings)) "an even number of forms in binding vector")
  `(let* ~(destructure bindings) ~@body))
=> nil

lein: Find-doc

(find-doc "macro expansion")
-------------------------
clojure.pprint/*print-suppress-namespaces*
  Don't print namespaces with symbols. This is particularly useful when
pretty printing the results of macro expansions
=> nil

The Gorilla Repl

  • lein gorilla
  • Launches a gorilla repl at

In Conclusion:

Clojure REPLs are Awesome!

  • The REPL provides and interactive environment
  • Great for testing, prototyping, and debugging

In Conclusion:

Clojure is Awesome!

  • Do you want to be awesome?
  • Try Clojure!