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}
- Lists:
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:
firstandrest - Constructed with
seq - Different from iterators:
first/restare non-destructive (do not change the internal state of a generator) - Allocates a new object encapsulating the index when
restis called (so it does create ephemeral garbage, price to be paid for its non-destructive nature)
- Seq abstraction:
- 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
- Changed somewhat in newer versions of Clojure (
lazy-seqinstead oflazy-cons) - Implement the same interface as non-lazy sequences, so can be freely mixed
- Not generators/iterators. Uses a thunk inside of a cons cell
- Details here: https://gist.github.com/finalfantasia/b20a2a2d3a3ac445e456fbbf2db38cae
- More: https://medium.com/@pwentz/laziness-in-clojure-3d83645bf7f3
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/recurre-use stack frames for efficient recursion - Equality:
=follows Henry Baker'segal? - 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,
letforms, 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