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 commute
commute
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…
@Raw
Why 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{}