Clojure Lisp for the Real World - YOW! Conferences · 2019-09-23 · Clojure Values 3 Long 6.022e23...
Transcript of Clojure Lisp for the Real World - YOW! Conferences · 2019-09-23 · Clojure Values 3 Long 6.022e23...
ClojureLisp for the Real World
@stuartsierraclojure.com
Stuart SierraRelevance, Inc.
Clojure/core
Clojure contributor
Values
Values
3
Values
3 + 2 = 5
Values
3let x =
Values
3let x =
let x = 5
Values
3let = 5
Values
3define = 5
Values
3
Values
3Immutable
Values
3Immutable Persistent
He ll o, world!" "
H e ll o , w o r l d ! \0
greeting
char greeting[14];strcpy(greeting, "Hello, World!");
H e ll o , w o r l d ! \0
greeting
char* name = greeting + 7;
name
H e ll o , b o r e d ! \0
greeting
name[0] = 'b';name[3] = 'e';
name
String greeting = new String("Hello, world!");
H e ll o , w o r l d !
java.lang.String
greeting
H e ll o , w o r l d !
java.lang.String
greeting
H e ll o , b o r e d !
java.lang.String
String alt = greeting.replace("world", "bored");
alt
H e ll o , w o r l d !
java.lang.String
greeting
Immutable
In the real world,values don't change.
Clojure Values
3 Long
6.022e23 Double
"Hello, world!" String
3.0M BigDecimal
9223372036854775808N BigInt
2/3 Ratio
Clojure Values
even? Symbol
:last-name Keyword
(print 99 "bottles") List
[3.14 :pi] Vector
{:x 3, "y" 4} Map
#{7 9 "foo"} Set
Clojure Values
ListO(1) at the head
O(n) anywhere else
Vector O(1) access
Map O(1) access
Set O(1) contains?
*
*
*
Immutable Persistent
Clojure Values(1 2 3)
[:a :b :c]
{:a 4}
#{3 7}
x
...
y x
...
x
...
x
...
y
...
x y
...
...
...
...
...
...
...
Clojure Values
ListO(1) at the head
O(n) anywhere else
Vector O(log32n) access
Map O(log32n) access
Set O(log32n) contains?
321 = 32322 = 1024323 = 32,768324 = 1,048,576325 = 33,554,432326 = 1,073,741,824327 = 34,359,738,368
log32 1000 < 2log32 10,000 < 3
log32 1,000,000 < 4log32 10,000,000 < 5
log32 1,000,000,000 < 6
In the real world,log32n is fast enough.
Code is Data
#{ {:first "Stuart" :last "Sierra"} {:first "Luke" :last "VanderHart"} {:first "Michael" :last "Fogus"} }
Code is Data
#{ {:first "Stuart" :last "Sierra"} {:first "Luke" :last "VanderHart"} {:first "Michael" :last "Fogus"} }
Code is Data
A set of maps
(+ 3 4 5)List
Symbol
(+ 3 4 5)List
Symbol
Function call
(defn average [& nums] (/ (reduce + nums) (count nums)))
(average 3 4 5)
ListSymbols
VectorSymbols
Function call
Host Interop.
(def m (ConcurrentHashMap. 100))
(.put m "key" "value")
constructor
method call
You
Lexer/Parser
Characters
AbstractSyntax Tree
Compiler/Interpreter
Clojure Reader
ClojureCompiler
You Characters
Data Structures
JVM Bytecode
Clojure Reader
You Characters
Data Structures
Macros
ClojureCompiler
JVM Bytecode
(let [file ...initializer...] (try ... do something ... (finally (.close file))))
(defmacro with-open [bindings & body] `(let ~bindings (try ~@body (finally (.close ~(first bindings)))))
(let [file ...initializer...] (try ... do something ... (finally (.close file))))
(with-open [file ...initializer...] ... do something ...)
Generic Data Access
#{ {:first "Stuart" :last "Sierra"} {:first "Luke" :last "VanderHart"} {:first "Michael" :last "Fogus"} }
Generic data access
A set of maps
Author[] authors = ...
String names[] = new names[authors.length];for (int i = 0; i < authors.length; i++) { names[i] = authors[i].getFirstName();}
(defn get-first-name [author] (get author :first))
(map get-first-name authors)("Stuart" "Luke" "Michael")
Higher-orderfunction
(map (fn [a] (get a :first)) authors)("Stuart" "Luke" "Michael")
Anonymous function
(map #(get % :first) authors)("Stuart" "Luke" "Michael")
Anonymous function
(map #(% :first) authors)("Stuart" "Luke" "Michael")
Maps are functions
(map #(:first %) authors)("Stuart" "Luke" "Michael")
Keywords are functions!
(map :first authors)("Stuart" "Luke" "Michael")
Keywords are functions
(map :first authors)
String names[] = new names[authors.length];for (int i = 0; i < authors.length; i++) { names[i] = authors[i].getName();}
vs.
(map function set-of-maps)
(map function set-of-maps)(map function vector-of-sets-of-maps)(map function list-of-vectors)(map function map-of-maps)
(map function set-of-maps)
(map function (resultset-seq query))
(map function (line-seq file))(map function (xml-seq xml-tree))
(map function (file-seq directory))
(map function list-of-vectors)(map function map-of-maps)
(map function vector-of-sets-of-maps)
mapfilter
reducecountsome
removereplace
Sequence API
and lots more...
ListVectorMap
ResultSetStream
DirectoryIterator
XML
and lots more...
Sequence Generators
mapfilter
reducecountsome
removereplace
Sequence API
and lots more...
ListVectorMap
ResultSetStream
DirectoryIterator
XML
and lots more...
Sequence Generators
× = Power
Polymorphism
or, OOP: the Good Parts
Multimethods(defmulti encounter (fn [a b] ... food chain comparison ...))
(defmethod encounter [:predator :predator] [a b] (fight a b))
(defmethod encounter [:predator :prey] [a b] (eat a b))
(defmethod encounter [:prey :predator] [a b] (eat b a))
(defmethod encounter [:prey prey] [a b] (run-away a) (run-away b))
Protocols(defprotocol Coercions "Coerce between various 'resource-namish' things." (as-file [x] "Coerce argument to a file.") (as-url [x] "Coerce argument to a URL."))
(extend-protocol Coercions nil (as-file [_] nil) (as-url [_] nil) String (as-file [s] (File. s)) (as-url [s] (URL. s)) File (as-file [f] f) (as-url [f] (.toURL (.toURI f))))
Records(def me {:name "Stuart" :state "NY" :city "Brooklyn"})
(type me) clojure.lang.PersistentHashMap
(:city me) "Brooklyn"
(defrecord Person [name state city])
(def me (->Person "Stuart" "NY" "Brooklyn"))
(type me) user.Person
(:city me) "Brooklyn"
Record + Protocols(defprotocol Coercions "Coerce between various 'resource-namish' things." (as-file [x] "Coerce argument to a file.") (as-url [x] "Coerce argument to a URL."))
(defrecord MyResource [protocol host path] Coercions (as-file [x] (java.io.File. path)) (as-url [x] (java.io.URL. (str protocol "://" host "/" path))))
Records(defrecord Person [name state city])
(def me (->Person "Stuart" "NY" "Brooklyn"))
(type me) user.Person
(:city me) "Brooklyn"
(ancestors (type me)) #{clojure.lang.IPersistentCollection java.io.Serializable clojure.lang.Associative clojure.lang.IPersistentMap clojure.lang.IKeywordLookup clojure.lang.IMeta clojure.lang.IObj java.util.Map java.lang.Iterable clojure.lang.Counted clojure.lang.IRecord clojure.lang.ILookup java.lang.Object clojure.lang.Seqable}
Types
(deftype SuperNewTree [size root])
(def me (->SuperNewTree 0 nil))
(type me) user.SuperNewTree
(ancestors (type me)) #{clojure.lang.IType java.lang.Object}
Existing Protocolsor Interfaces
Your newprotocol here
Existing Types
Existing Method Implementations
extend(very hard in Java)
Your new type here
extend
The Expression Problem
Concurrency
Concurrency
Parallelism
State
class Invoice { private Date date;
public Date getDate() { return this.date; }
public void setDate(Date date) { this.date = date; }}
class Date { public void setDay(int day); public void setMonth(int month); public void setYear(int year);}
Mutable!
class Invoice { private Date date;
public Date getDate() { return this.date; }
public void setDate(Date date) { this.date = date; }}
Better notchange it!
Better notchange it!
class Invoice { private Date date;
public Date getDate() { return this.date.clone(); }
public void setDate(Date date) { this.date = date.clone(); }}
Defensivecopying
Programming with immutable values
means never having to say you're sorry.
today
June 20
today
June 20 June 21
Identity
State
today
Value Value
June 20 June 21
Identity
State
today
function
Value Value
functionJune 20 June 21
Identity
State
Past
function
Present Future
function
today
June 20 June 21June 19
June 20
Identity
State
Past
June 21June 19 function
Present Future
function
today
The future is a function of the past.
Mutable References
Ref Atom
Var Agent
readers
Atomwriter
writer
abortretry spin
commit
Atom(def tick (atom 1))(deref tick) 1
tick
1
Atom(def tick (atom 1))(deref tick) 1
(swap! tick inc)@tick 2
tick
1
2
inc
Atom(def tick (atom 1))(deref tick) 1
(swap! tick inc)@tick 2
(swap! tick + 10)@tick 12
tick
2
12
+10
value value value value
value value value value
value value value value
identity
identityidentity
value value value value
value value value value
value value value value
identity
identityidentity
Refwriter
writer
readers
abortretryRef
transaction
wait
commit
Ref(def A (ref 1))(def B (ref 10))
A 1
10B
Ref(def A (ref 1))(def B (ref 10))
(dosync (alter A inc) (alter B + 10))
A 1
10
20
inc
+10
B
2
transaction
Ref(def A (ref 1))(def B (ref 10))
(dosync (alter A inc) (alter B + 10))
A
20
B
2
@A 2@B 20
AtomicConsistentIsolatedDurable
Database Transactions
AtomicConsistentIsolated
Clojure Transactions
Agentactionaction
readers
action queue
Agent Thread Pool
Agent(def A (agent 1))
A 1queue
Agent(def A (agent 1))
(send A inc)@A 1
A 1inc
queue
Agent(def A (agent 1))
(send A inc)@A 1
A 1inc
2@A 2
queue
Agents != Actors
Agents != Actors
Agent Actor
Reads
Actions / Messages
Distributed?
Cheap,do not block
Send message,wait for response
Any functionOnly what receiver
understands
No Yes
Agents != Actors
Agent Actor
Reads
Actions / Messages
Distributed?
Cheap,do not block
Send message,wait for response
Any functionOnly what receiver
understands
No Yes
Agents != Actors
Agent Actor
Reads
Actions / Messages
Distributed?
Cheap,do not block
Send message,wait for response
Any functionOnly what receiver
understands
No Yes
Agents != Actors
Agent Actor
Reads
Actions / Messages
Distributed?
Cheap,do not block
Send message,wait for response
Any functionOnly what receiver
understands
No Yes
Ref Atom
Var Agent
Concurrency
• No locking in user code
• No deadlocks
• Guaranteed forward progress
• Writes never block reads
More
• Me: stuartsierra.com,@stuartsierra
• Us: thinkrelevance.com,clojure.com
• Clojure: clojure.org
Image Credits: openclipart.org, pdtextures.blogspot.com,Clojure logo by Tom Hickey