Clojure Training with Lambda Next · Day 1 - olange/learning-clojure GitHub Wiki
21-23.10.2013 in Geneva, with Christophe Grand and Edmund Jackson.
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, likecons
ormap
, 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.