http://www.lvh.io/ClojureIntro/
https://www.github.com/lvh/ClojureIntro
map, filter, reduce?
A lot of content
(apply modern-lisp @jvm)
(f a b c)
just a different spelling for:
f(a, b, c)
Lots of reasons:
How long does it take to try something?
C-c C-e
(Partial) static typing
core.typed
Software transactional memory
Asynchronous programming
Logic programming
Most other languages are the same or worse
Lisp is a ball of mud
$LANG is a shiny diamond
ArrayList
Date :-(
“85% functional”
BigInt, BigDecimal…
int, float…
java.util.Date
12 Jan 1991, 18 Mar 2002
How many dates?
Date, change day, month, year
Date, different date!
Date was a mistake
someObject.setWhatever
E.g. collections
{3, 5}, {3, 5, 7}
How many sets?
HashSet, add/remove some elements
Set, different set!
Imperative, single-threaded programming!
No concept of time or transitions
“No man can cross the same river twice.”
– Heraclitus, ~500 BC
The river doesn’t stop existing just because nobody is around to call it a river…
E.g. logs, source control
What if they destroyed data?
They would be totally useless
(f x) gives you new data structure
transient, persistent!
Keeping old versions around is cheap!
Bit-partitioned hash tries
| Depth | Nodes |
| 0 | 32 |
| 1 | 1024 |
| 2 | ~32k |
| 3 | ~1M |
| 4 | ~32M |
| Conventional | Clojure | |
|---|---|---|
| References | Direct | Indirect |
| Objects | Mutable | Immutable |
| Concurrency? | Lock-and-pray | Ref type semantics |
Encapsulation doesn’t fix this!
Indirect reference to immutable value
Doesn’t affect readers; not affected by readers
| ref | agent | atom | volatile | (vars) | |
|---|---|---|---|---|---|
| Shared? | ✓ | ✓ | ✓ | ✗ | ✗ |
| Synchronous? | ✓ | ✗ | ✓ | ✓ | ✓ |
| Coordinated? | ✓ | ✗ | ✗ | ✗ | ✗ |
(transition-fn ref func [& args])
new-state: (func current-state &args)
@ref
Deep similarity!
(atom init-val)
(swap! some-atom f & args) to modify
compare-and-set! too (bit more low level)
reset! to rudely modify
(def n (atom 1)) (swap! n inc) ;; => 2 (swap! n * 10) ;; => 20
swap!ref reference type
dosync to make transactions
alter and commute to modify
ref-set to rudely modify)
ensure to check the current value
(def n (ref "xyzzy")) @n ;; => "xyzzy" (dosync (prn @n)) ;; xyzzy
(def n (ref 0)) (alter n inc) ;; IllegalStateException ;; No transaction running ... @n ;; => 0 (dosync (alter n inc)) @n ;; => 1
(defn transfer [amount from to] (dosync (alter from - amount) (alter to + amount)))
alter vs commutecommute allows more orderings
func
alter vs commute(def counter (ref 0)) (defn slow-inc! [alter-fn counter] (dosync (Thread/sleep 100) (alter-fn counter inc))) (defn bombard-counter! [n f counter] (apply pcalls (repeat n #(f counter))))
alter performance(dosync (ref-set counter 0)) (time (doall (bombard-counter! 20 (partial slow-inc! alter) counter))) ;; => (1 2 4 3 5 6 13 12 9 8 7 11 10 15 17 14 20 18 19 16) ;; "Elapsed time: 2025.646 msecs" ;; 20 incs * 100 ms = 2000 ms...
commute performance(dosync (ref-set counter 0)) (time (doall (bombard-counter! 20 (partial slow-inc! commute) counter))) ;; => (3 6 1 1 7 1 1 8 8 8 8 8 10 14 15 15 19 15 15 19) ;; "Elapsed time: 305.23 msecs" ;; Without delay: virtually instant, so 3 txn attempts
Pretty intricate
Implementation has a number of clever tricks…
@RawWhy not locks?
Segmentation fault
Manual memory management
versus
GC and lifetime analysis
One atom, usually with a map, to hold state:
(def app-state (atom {:user-name "lvh" :todo-items ["Take out trash" "Present Clojure intro"] :done-items #{"Make slides"}}))
I don’t know for sure, but I have some hypotheses:
Real answer is probably all of the above & more :-)
(1.7, beta)
Just monoids in the category of endofunctors!
map
(map f coll)
((f x) for all x in coll)
(map inc [1 2 3]) ;; => (2 3 4)
filter
(filter f coll)
(all of the x in coll, if (f x))
(filter even? [1 2 3]) ;; => (2)
reduce
(reduce f coll)
(accumulate over coll with f)
(reduce + [1 2 3 4]) ;; => 10
We kept implementing map, reduce, etc.
Extract the essence of map, reduce…
(map f)
vs.
(map f coll)
(map f)
vs.
(partial map f)
(Examples adapted from Rich Hickey’s Strange Loop talk)
;; Build concrete collections (into airplane process-bags pallets) ;; Build a lazy sequence (sequence process-bags pallets) ;; "Reduce" a collection (transduce (comp process-bags (map weigh)) + pallets) ;; core.async channels (chan 1 process-bags)
(def xform
(comp (partial map inc)
(partial filter odd?)
(partial map #(* 3 %))))
(xform [1 2 3])
(comp (mapcat unbundle-pallet) (take-while (complement ticking?)) (filter (complement smells-like-food?)) (map label-heavy-items) (take max-plane-capacity))
Many basic “language features” are macros:
defn, and, cond…
(Just like Racket)
x.m(a, b, c)
Which m?
m depends on type of x
Not just type of x, but the value of x.m
x.m on the instance
__getattr(ibute)__ hacks
x still picks the m!
(Smalltalk parlance)
x ← m(a, b, c)
x
Routing logic: f(x)
Icecap example?
core.logic
Going back is painful ;-)
Bad type systems
def map[B, That](f: A => B)(implicit bf:
CanBuildFrom[Repr, B, That]): That
interface{}