Clojure Training with Lambda Next · Day 1 - olange/learning-clojure GitHub Wiki

21-23.10.2013 in Geneva, with Christophe Grand and Edmund Jackson.

Day 1 / Day 2 / Day 3

Live-REPL session: Edmund typing in Emacs, evaluating expressions, Christophe and Edmund discussing the results. Christophe typing in Eclipse and Counterclockwise, evaluating expressions, further discussing the results.

Datastructures

Fundamentals

  • Vectors: IFn, get, nth, assoc associative
  • Lists: first, rest, nthrest mainly used for code as data
  • Maps: IFn, get, assoc, keyword (:c {:a 1 :b 2} -> faster access)
  • Keywords: namespace
  • Sets: IFn, contains? _unordered; contains? _

Accessing

  • (:k {...}) ;; faster
  • (get {...} :k) ;; generic, but slow
  • ({...} :k) ;; Function-like
  • contains? key ;; works for sets, maps, vectors... but checks for the keys (watch out when using vectors)
  • some fn struct ;; works for sets, maps, vectors

Manipulating

  • get-in
  • assoc-in
  • update-in
  • fnil
  • conj preserves data structure
  • (conj (sorted-set 10 -10 20 3) -100)
  • disj
  • dissoc
  • pop there is no push, conj is the new push
  • (subseq (sorted-set 10 -10 20 3) > 10 <15) also works on maps
  • (rsubseq (sorted-set 10 -10 20 3) >= -10 < 15) also works on maps
  • sorted-set-by / compare compare calls the default comparator for the underlying datatype
  • (sorted-set-by (fn [x y] (- (compare x y))) [2013 10 5] [2012 01 01] [2010 12 05])
  • subvec cheap creation, view on the original vector, but changing it requires copying

Functions and branching

  • fn/defn/#()
  • apply
  • (-> "(+ %1 %2)" read-string eval)
  • (boolean (Boolean. false)) beware the ill-behaved Boolean. Java constructor)
  • if :then :else the only true branching form available in Clojure, all others are macros built on top of it
  • when comes with an implicit do; prefered to if without else
  • (and test expr return expr) and returns the last expression if it evaluates to true, but this is uneasy to grasp
  • (cond test expr result :else :default)
  • (macroexpand-1 '(and a b))

Lexical scoping

  • let
  • (let [a 42] (let [a 430] a)) nested scope; the nearest one wins
  • (let [b 42] (fn [a] (*b a))) the compiler captures the links to the function's environment variable an expression is refering to; Clojure does flat closure; have a look at LISP in small pieces / Principes d'implémentation de Scheme et LISP is the second edition

## Statements do / loop

  • loop: don't use it

## Sequences

  • (seq [1 2 3 4]) sequential view on the collection
  • (seq [] / {} / "") seq of an empty collection returns nil
  • a convention in the language: if the collection comes first, like in conj, it is an operation that is specific to and will preserve that datatype; sequences functions, like cons or map, take the collection last and always return a sequence
  • resultset-seq / iterator-seq the first one turns a JDBC-resultset into a sequence; the second one any iterator; both fill the gaps between Java and Clojure worlds
  • empty? same as (not (seq coll))

Sequential and associative destructuring

Destructuring does not need to match the length of the collection: can be shorter than the collection or longer; variable receives nil if no matching value.

  • (if-let [[ x & xs ] (seq [1 2 3])] ...) => x: 1, xs: (2 3)
  • (let [[[a b] [c d]] [[0 1] [2 3]]] [a b c d]) => [0 1 2 3]
  • (let [{:keys [fname lname mname] :as m :or {mname "-"}} {:fname "Edmund" :lname "Jackson" }] [fname lname mname]) => ["Edmund" "Jackson" "-"]
  • (let [{:strs [fname lname mname]} {"fname" "Edmund" "lname" "Jackson"}] [fname lname mname]) => ["Edmund" "Jackson" nil]
  • (let [{f "fname" l "lname"} {"fname" "Edmund" "lname" "Jackson"}] [f l]) => ["Edmund" "Jackson"] associative

## Map, reduce and other high-order functions

High-order functions always call seq on the collection in the arguments.

  • (map (fnil dec 0) [1 2 nil]) => [0 1 -1]
  • (reduce conj [] #{1 2 3}) => [1 2 3]
  • (into {} [[:a 1] [:b 2]]) => {:a 1, :b 2}
  • (filter even? [10 11 12 13 14]) => (10 12 14)
  • (remove even? [10 11 12 13 14]) => (11 13)
  • (interleave [1 2 3] [:a :b :c]) => (1 :a 2 :b 3 :c)
  • (group-by even? [10 11 12 13 14]) => {true [10 12 14], false [11 13]}
  • (partition-by #(quot % 2) => ((10 11) (12 13) (14))
  • (map #(repeat (rand-int 10) %) (range 10))
  • (mapcat #(repeat (rand-int 10) %) (range 10)) flattens the previous expression; mapcat is the only high-order function that might return a bigger structure as the input
  • (split-with odd? [1 1 1 2 3 3 3]) => [(1 1 1) (2 3 3 3)]
  • (for [x [1 2 3] y (range x) :let [z (+ x y)]] [x y z]) => ([1 0 1] [2 0 2] [2 1 3] [3 0 3] [3 1 4] [3 2 5])
  • (for [x [1 2 3] y (range x) :let [z (+ x y)] :when (even? z)] [x y z]) => ([2 0 2] [3 1 4])

## Chaining high-order functions

  • (->> [1 2 3 4 5 6 7 8] (map inc) (filter even?) (partition 2)) => ((2 4) (6 8)) double stitch, thread last, works with sequences, allows to chain
  • (-> [1 2 3] (conj 10) (conj 20)) => [1 2 3 10 20] thread first, works with collections, allows to apply multiple transformations to a structure
  • (->> [1 2 3 4 5] (map inc) (filter even?) (into #{})) => #{2 4 6}
  • some->
  • (doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2)) returns the first expression, while still evaluating all others; along the way, (doto expr (prn expr)) might be used to debug
  • comp dot-free style
  • keep

Pipeline game (photo). Remember that longer pipelines put pressure on the memory (GC); reducers come to the help.

Tron Bots hacking, starting from Tron Geneva 2013.