Clojure for Lisp Programmers - tifojo/knowledge_base GitHub Wiki

Clojure for Lisp Programmers

Notes on Rich Hickey's "Clojure for Lisp Programmers"

Part 1

Clojure for Lisp Programmers (Part 1)

Design overview

  • Syntax designed to alleviate nested grouping symbols
  • Claim that VMs are the "platforms of the future" maybe hasn't 100% held up, but the decision to host on the JVM enables first-class access to Java and its libraries
  • Industry has made deep investment in JVM "performance, scalability, security libraries, etc."
  • Concurrency requires immutable-by-default data structures
  • Richer set of built-in data structures compared to existing Lisps
  • Focus on abstract interfaces rather than concrete representations

Lisp details

  • Lisp-1 (unified namespace for functions and variables)
  • Clojure reader is side-effect free
  • Runtime-compiled to JVM bytecode
  • Tail-call optimization not possible on JVM, so special forms needed for tail calls (loop/recur)
  • Convention of square brackets where a function call or special form is not intended

First-class data structures

  • Lists, vectors, maps (hashed and sorted), sets (hashed and sorted)
  • Structurally recursive
  • Uniform operation to extend data structure (conj)
  • Literal syntax:
    • Lists: (1 2 3)
    • Vectors: [1 2 3]
    • Maps: {:a 1, :b 2} (comma is whitespace)
    • Sets: #{a b c}

Abstractions

  • Scheme/CL define too many functions in terms of concrete types (often lists)
  • Interfaces/virtual function calls in JVM enable efficient genericity
  • Abstracting away the cons cell:
    • Seq abstraction: first and rest
    • Constructed with seq
    • Different from iterators: first/rest are non-destructive (do not change the internal state of a generator)
    • Allocates a new object encapsulating the index when rest is called (so it does create ephemeral garbage, price to be paid for its non-destructive nature)
  • Each abstract data type can have multiple implementations, depending on e.g. size of the data set
  • Because "modifications" return a new value, implementation of the returned object can be different
  • Maps, vectors, sets implement callability (function of the key/index):
    • (let [m {:a 1}] (m :a)) => 1

Lazy sequences

Functional programming

  • Core data structures all immutable (with good performance)
  • Handy function to combine maps:
    • (merge-with + {:a 1} {:a 2, :b 3}) => {:a 3, :b 3}
  • For tail calls: loop/recur re-use stack frames for efficient recursion
  • Equality: = follows Henry Baker's egal?
  • Overloading:
    • Multiple function bodies within one definition
    • Fast dispatch without conditionals

Part 2

Clojure for Lisp Programmers (Part 2)

Destructuring

  • "Abstract structural binding" in parameter lists, let forms, etc.
  • Vectors destructure sequential things:
    • (let [[a b] '(1 2)] [a b]) => [1 2]
  • Maps destructure associative things:
    • (let [{a :a, b :b} {:a 1, :b 2}] [a b]) => [1 2]

Polymorphism

  • Multimethods:
    • Dispatch function is an arbitrary function of method's arguments
    • Cached after the first invocation

Metadata

  • Attached to data, but does not affect equality semantics
  • Literal metadata in reader can be used to give compiler hints

Concurrency

  • Mutable objects/shared data structures are a problem for concurrency
  • OOP creates a difficult-to-understand "graph of interconnected changing things"
  • Need idiomatic immutable-by-default semantics in the language
  • Indirect references to immutable data structures
  • Software transactional memory