This introduction to Clojure was given to the Utah Java Users Group Aug. 15. It's main focus was on Clojure's time model and how the design of Clojure separates (decomplects) many concepts which are all implemented onto of Objects in Java, and other OO languages. This is the abstract for the original talk:
Tony Hoare famously said "There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult." Clojure is a functional Lisp that targets, among other platforms, the JVM and strives to enable the former approach to building software.
In its pursuit of simplicity Clojure encourages the use of pure functions, sequence abstractions which allow for lazy and parallel processing of data, persistent (immutable) data structures, and a novel way of dealing with state as a succession of values. While these concepts may sound intimidating for those unfamiliar with functional programming, they are actually less complicated than many programming constructs that programmers use everyday.
This talk will cover these concepts and the motivation behind them. You will learn the basics of Clojure programming and will be given a taste of what developing an application in Clojure is like.
3. "There are two ways of constructing a
software design: One way is to make it so
simple that there are obviously no
deficiencies, and the other way is to make it
so complicated that there are no obvious
deficiencies. The first method is far more
difficult."
Tony Hoare, 1980 Turing Award Lecture
4. Clojure is a functional Lisp
that targets the JVM and
enables simpler software design.
5. Clojure is a functional Lisp
that targets the JVM and
enables simpler software design.
12. (defmacro when
"Evaluates test. If logical true,
evaluates body in an implicit do."
[test & body]
(list 'if test (cons 'do body)))
13. (defmacro when
"Evaluates test. If logical true,
evaluates body in an implicit do."
[test & body]
(list 'if test (cons 'do body)))
(macroexpand '(when (= code :RED)
(launch-missiles!)
(sound-the-alarm!)))
14. (defmacro when
"Evaluates test. If logical true,
evaluates body in an implicit do."
[test & body]
(list 'if test (cons 'do body)))
(macroexpand '(when (= code :RED)
(launch-missiles!)
(sound-the-alarm!)))
=> (if (= code :RED)
(do
(launch-missiles!)
(sound-the-alarm!)))
18. Paradigms as Libraries
Design by Contract a’la Eiffel - core.contracts
Logic Programming a’la Prolog - core.logic
Lightweight threads + channels a’la Go - core.async
19. Paradigms as Libraries
Design by Contract a’la Eiffel - core.contracts
Logic Programming a’la Prolog - core.logic
Lightweight threads + channels a’la Go - core.async
Optional/Gradual Type system - core.typed
20. Paradigms as Libraries
Design by Contract a’la Eiffel - core.contracts
Logic Programming a’la Prolog - core.logic
Lightweight threads + channels a’la Go - core.async
Optional/Gradual Type system - core.typed
Actor model a’la Erlang - pulsar
21. Paradigms as Libraries
Design by Contract a’la Eiffel - core.contracts
Logic Programming a’la Prolog - core.logic
Lightweight threads + channels a’la Go - core.async
Optional/Gradual Type system - core.typed
Actor model a’la Erlang - pulsar
And more...
22. “If you give someone Fortran, he has Fortran.
If you give someone Lisp, he has any
language he pleases.”
Guy Steele
23. Clojure is a functional Lisp
that targets the JVM and
enables simpler software design.
43. public static int square(int x) {
return x * x;
}
Value
}Pure Function
44. public static int square(int x) {
return x * x;
}
import java.util.Date;
public static Date oneYearFrom(Date date)
{
date.setYear(date.getYear()+1);
return date;
}
45. public static int square(int x) {
return x * x;
}
import java.util.Date;
public static Date oneYearFrom(Date date)
{
date.setYear(date.getYear()+1);
return date;
}
Reference Object
46. public static int square(int x) {
return x * x;
}
import java.util.Date;
public static Date oneYearFrom(Date date)
{
date.setYear(date.getYear()+1);
return date;
}
Reference Object
Mutable!
Impure Function with side effects
47. public static int square(int x) {
return x * x;
}
import java.util.Date;
public static Date oneYearFrom(Date date)
{
date.setYear(date.getYear()+1);
return date;
}
import org.joda.time.DateTime;
public static DateTime oneYearFrom(DateTime date)
{
MutableDateTime temp = new MutableDateTime(date);
temp.addYears(1);
return temp.toDateTime();
}
48. public static int square(int x) {
return x * x;
}
import java.util.Date;
public static Date oneYearFrom(Date date)
{
date.setYear(date.getYear()+1);
return date;
}
import org.joda.time.DateTime;
public static DateTime oneYearFrom(DateTime date)
{
MutableDateTime temp = new MutableDateTime(date);
temp.addYears(1);
return temp.toDateTime();
} Value Object
49. public static int square(int x) {
return x * x;
}
import java.util.Date;
public static Date oneYearFrom(Date date)
{
date.setYear(date.getYear()+1);
return date;
}
import org.joda.time.DateTime;
public static DateTime oneYearFrom(DateTime date)
{
MutableDateTime temp = new MutableDateTime(date);
temp.addYears(1);
return temp.toDateTime();
} Value Object
Pure Function
50. public static int square(int x) {
return x * x;
}
import java.util.Date;
public static Date oneYearFrom(Date date)
{
date.setYear(date.getYear()+1);
return date;
}
import org.joda.time.DateTime;
public static DateTime oneYearFrom(DateTime date)
{
//MutableDateTime temp = new MutableDateTime(date);
//temp.addYears(1);
return date.plusYears(1);
}
Pure Function
Nice Immutable API
51. public static int square(int x) {
return x * x;
}
import java.util.Date;
public static Date oneYearFrom(Date date)
{
date.setYear(date.getYear()+1);
return date;
}
import org.joda.time.DateTime;
public static DateTime oneYearFrom(DateTime date)
{
//MutableDateTime temp = new MutableDateTime(date);
//temp.addYears(1);
return date.plusYears(1);
}
52. public static int square(int x) {
return x * x;
}
import java.util.Date;
public static Date oneYearFrom(Date date)
{
date.setYear(date.getYear()+1);
return date;
}
import org.joda.time.DateTime;
public static DateTime oneYearFrom(DateTime date)
{
//MutableDateTime temp = new MutableDateTime(date);
//temp.addYears(1);
return date.plusYears(1);
}
56. ReferencesValues
primitives
java.lang wrappers
(Boolean, Byte, Character,
Double, Float, Integer, Long,
Short, String)
other wrappers...
e.g. UUID, URL, URI
Date
BigInteger,BigDecimal, etc.
Collections
ArrayList,ArrayDeque,TreeSet,
HashSet, TreeMap, HashMap,
LinkedList,PriorityQueue,etc...
57. ReferencesValues
primitives
java.lang wrappers
(Boolean, Byte, Character,
Double, Float, Integer, Long,
Short, String)
other wrappers...
e.g. UUID, URL, URI
Date
BigInteger,BigDecimal, etc.
Collections
ArrayList,ArrayDeque,TreeSet,
HashSet, TreeMap, HashMap,
LinkedList,PriorityQueue,etc...
Default for domain objects
58. ReferencesValues
primitives
java.lang wrappers
(Boolean, Byte, Character,
Double, Float, Integer, Long,
Short, String)
other wrappers...
e.g. UUID, URL, URI
Date
BigInteger,BigDecimal, etc.
Libraries
joda-time, joda-money
google-guava
Collections
ArrayList,ArrayDeque,TreeSet,
HashSet, TreeMap, HashMap,
LinkedList,PriorityQueue,etc...
Default for domain objects
59. ReferencesValues
primitives
java.lang wrappers
(Boolean, Byte, Character,
Double, Float, Integer, Long,
Short, String)
other wrappers...
e.g. UUID, URL, URI
Date
BigInteger,BigDecimal, etc.
Libraries
joda-time, joda-money
google-guava
Collections
ArrayList,ArrayDeque,TreeSet,
HashSet, TreeMap, HashMap,
LinkedList,PriorityQueue,etc...
Immutable Collections!
Default for domain objects
60. ReferencesValues
primitives
java.lang wrappers
(Boolean, Byte, Character,
Double, Float, Integer, Long,
Short, String)
other wrappers...
e.g. UUID, URL, URI
Date
BigInteger,BigDecimal, etc.
Libraries
joda-time, joda-money
google-guava
Collections
ArrayList,ArrayDeque,TreeSet,
HashSet, TreeMap, HashMap,
LinkedList,PriorityQueue,etc...
Immutable Collections!
+ They are values!
Default for domain objects
61. ReferencesValues
primitives
java.lang wrappers
(Boolean, Byte, Character,
Double, Float, Integer, Long,
Short, String)
other wrappers...
e.g. UUID, URL, URI
Date
BigInteger,BigDecimal, etc.
Libraries
joda-time, joda-money
google-guava
Collections
ArrayList,ArrayDeque,TreeSet,
HashSet, TreeMap, HashMap,
LinkedList,PriorityQueue,etc...
Immutable Collections!
+ They are values!
- Copy on write (very good impls though)
Default for domain objects
62. ReferencesValues
primitives
java.lang wrappers
(Boolean, Byte, Character,
Double, Float, Integer, Long,
Short, String)
other wrappers...
e.g. UUID, URL, URI
Date
BigInteger,BigDecimal, etc.
Libraries
joda-time, joda-money
google-guava
Collections
ArrayList,ArrayDeque,TreeSet,
HashSet, TreeMap, HashMap,
LinkedList,PriorityQueue,etc...
Immutable Collections!
+ They are values!
- Copy on write (very good impls though)
- Can’t help with nesting
Default for domain objects
63. ReferencesValues
primitives
java.lang wrappers
(Boolean, Byte, Character,
Double, Float, Integer, Long,
Short, String)
other wrappers...
e.g. UUID, URL, URI
Date
BigInteger,BigDecimal, etc.
Libraries
joda-time, joda-money
google-guava
Collections
ArrayList,ArrayDeque,TreeSet,
HashSet, TreeMap, HashMap,
LinkedList,PriorityQueue,etc...
Immutable Collections!
+ They are values!
- Copy on write (very good impls though)
- Can’t help with nesting
Default for domain objects
78. Explicit Ref Types
atom
ref
Persistent Collections
ReferencesValues
primitives
Java’s wrappers & libs
Collections
List, Vector, HashMap,
TreeMap, ArrayMap,
StructMap, Queue, HashSet,
TreeSet, LazySeq, defrecords
+ They are values
+ Structural Sharing
+ Memory efficient
+ Fast
+ Everything nests
The rest of Clojure
and its ecosystem is
built on these!
90. (def data {:nested [0 1 {:double "nested"}]})
(get-in data [:nested 2 :double])
Nesting
91. (def data {:nested [0 1 {:double "nested"}]})
(get-in data [:nested 2 :double])
Nesting
First map key Index into vector
Nested map key
92. (def data {:nested [0 1 {:double "nested"}]})
(get-in data [:nested 2 :double])
> "nested"
Nesting
93. (update-in data [:nested 2 :double] upper-case)
(get-in data [:nested 2 :double])
> "nested"
(def data {:nested [0 1 {:double "nested"}]})
Nesting
94. (update-in data [:nested 2 :double] upper-case)
(get-in data [:nested 2 :double])
> "nested"
(def data {:nested [0 1 {:double "nested"}]})
Nesting
Same path
95. (update-in data [:nested 2 :double] upper-case)
(get-in data [:nested 2 :double])
> "nested"
(def data {:nested [0 1 {:double "nested"}]})
Nesting
Fn applied to nested value
96. (get-in data [:nested 2 :double])
> "nested"
(def data {:nested [0 1 {:double "nested"}]})
Nesting
(update-in data [:nested 2 :double] upper-case)
> {:nested [0 1 {:double "NESTED"}]}
Entire “updated” data is
returned as a value
97. (def data {:nested [0 1 {:double "nested"}]})
(get-in data [:nested 2 :double])
> "nested"
Nesting
(update-in data [:nested 2 :double] upper-case)
> {:nested [0 1 {:double "NESTED"}]}
(assoc-in data [:nested 2 :another] "val")
98. (def data {:nested [0 1 {:double "nested"}]})
(get-in data [:nested 2 :double])
> "nested"
Nesting
(update-in data [:nested 2 :double] upper-case)
> {:nested [0 1 {:double "NESTED"}]}
(assoc-in data [:nested 2 :another] "val")
New key
99. (def data {:nested [0 1 {:double "nested"}]})
(get-in data [:nested 2 :double])
> "nested"
Nesting
(update-in data [:nested 2 :double] upper-case)
> {:nested [0 1 {:double "NESTED"}]}
(assoc-in data [:nested 2 :another] "val")
New value
New key
122. public class GameBoard {
!
! private List<ColoredSpace> spaces = new ArrayList<ColoredSpace>();
! private CardDeck cardDeck = new CardDeck();
! private List<Player> players = new ArrayList<Player>();
! // Points to player whose turn it is next
! private int playerPointer = 0;
! // Players position on the board
! private Integer[] playerPositions;
.... }
123. public class GameBoard {
!
! private List<ColoredSpace> spaces = new ArrayList<ColoredSpace>();
! private CardDeck cardDeck = new CardDeck();
! private List<Player> players = new ArrayList<Player>();
! // Points to player whose turn it is next
! private int playerPointer = 0;
! // Players position on the board
! private Integer[] playerPositions;
.... }
public class Player {
! private String name;
! public Player(String name) {
! ! this.name = name;
! }
! public String getName() {
! ! return name;
! }
}
128. distinct filter remove for keep keep-indexed
cons concat lazy-cat mapcat cycle interleave
interpose rest next fnext nnext drop
drop-while nthnext for take take-nth
take-while butlast drop-last for flatten
reverse sort sort-by shuffle split-at split-
with partition partition-all partition-by map
pmap mapcat for replace reductions map-indexed
seque first ffirst nfirst second nth when-
first last rand-nth zipmap into reduce set vec
into-array to-array-2d frequencies group-by
apply not-empty some reduce seq? every? not-
every? not-any? empty? some filter doseq dorun
doall realized? assoc get get-in assoc-in
update-in peek pop subvec conj cons into
129. It is better to have 100
functions operate on
one data structure
than 10 functions on
10 data structures.
Alan Perlis
142. (def colors [:purple :yellow :blue :orange :green :red])
user> (find-doc "cycle")
-------------------------
clojure.core/check-cyclic-dependency
([path])
Detects ....
-------------------------
clojure.core/cycle
([coll])
Returns a lazy (infinite!) sequence of repetitions of the items in coll.
ProTip, use find-doc to search for fns
143. (def colors [:purple :yellow :blue :orange :green :red])
user> (find-doc "cycle")
-------------------------
clojure.core/check-cyclic-dependency
([path])
Detects ....
-------------------------
clojure.core/cycle
([coll])
Returns a lazy (infinite!) sequence of repetitions of the items in coll.
159. Play the game
public class GameBoard {
! public void play() {
! ! while (nextTurn());
! ! System.out.println(“The winning player is:”
+ players.get(playerPointer).getName());
! }
160. ! // Player pulls a card from the top of deck and moves player
! private boolean nextTurn() {
! ! // Player selects card from top of deck
! ! Card currCard = cardDeck.takeTopCard();
! ! // If the game has ended, return now
! ! if (movePlayerOnBoard(currCard)) return false;
! ! // Next players turn
! ! playerPointer = (playerPointer + 1) % players.size();
! ! // Game has not ended yet
! ! return true;
! }
Play the game
public class GameBoard {
! public void play() {
! ! while (nextTurn());
! ! System.out.println(“The winning player is:”
+ players.get(playerPointer).getName());
! }
! // Player pulls a card from the top of deck and moves player
! private boolean nextTurn() {
! ! // Player selects card from top of deck
! ! Card currCard = cardDeck.takeTopCard();
! ! // If the game has ended, return now
! ! if (movePlayerOnBoard(currCard)) return false;
! ! // Next players turn
! ! playerPointer = (playerPointer + 1) % players.size();
! ! // Game has not ended yet
! ! return true;
! }
161. ! // Player pulls a card from the top of deck and moves player
! private boolean nextTurn() {
! ! // Player selects card from top of deck
! ! Card currCard = cardDeck.takeTopCard();
! ! // If the game has ended, return now
! ! if (movePlayerOnBoard(currCard)) return false;
! ! // Next players turn
! ! playerPointer = (playerPointer + 1) % players.size();
! ! // Game has not ended yet
! ! return true;
! }
Play the game
public class GameBoard {
! public void play() {
! ! while (nextTurn());
! ! System.out.println(“The winning player is:”
+ players.get(playerPointer).getName());
! }
! // Player pulls a card from the top of deck and moves player
! private boolean nextTurn() {
! ! // Player selects card from top of deck
! ! Card currCard = cardDeck.takeTopCard();
! ! // If the game has ended, return now
! ! if (movePlayerOnBoard(currCard)) return false;
! ! // Next players turn
! ! playerPointer = (playerPointer + 1) % players.size();
! ! // Game has not ended yet
! ! return true;
! }
Reference Object Land
162. ! // Player pulls a card from the top of deck and moves player
! private boolean nextTurn() {
! ! // Player selects card from top of deck
! ! Card currCard = cardDeck.takeTopCard();
! ! // If the game has ended, return now
! ! if (movePlayerOnBoard(currCard)) return false;
! ! // Next players turn
! ! playerPointer = (playerPointer + 1) % players.size();
! ! // Game has not ended yet
! ! return true;
! }
Play the game
public class GameBoard {
! public void play() {
! ! while (nextTurn());
! ! System.out.println(“The winning player is:”
+ players.get(playerPointer).getName());
! }
! // Player pulls a card from the top of deck and moves player
! private boolean nextTurn() {
! ! // Player selects card from top of deck
! ! Card currCard = cardDeck.takeTopCard();
! ! // If the game has ended, return now
! ! if (movePlayerOnBoard(currCard)) return false;
! ! // Next players turn
! ! playerPointer = (playerPointer + 1) % players.size();
! ! // Game has not ended yet
! ! return true;
! }
Reference Object Land
163. (defn next-move
"Takes the game forward one move by drawing a card for the
current player and moving them accordingly."
[game]
164. (defn next-move
"Takes the game forward one move by drawing a card for the
current player and moving them accordingly."
[game]
Docstring attached as metadata
165. (defn next-move
"Takes the game forward one move by drawing a card for the
current player and moving them accordingly."
[game]
(let [{:keys [decks board player-index players]} game
166. (defn next-move
"Takes the game forward one move by drawing a card for the
current player and moving them accordingly."
[game]
(let [{:keys [decks board player-index players]} game
Bind the keys we need with desctructuring
167. (defn next-move
"Takes the game forward one move by drawing a card for the
current player and moving them accordingly."
[game]
(let [{:keys [decks board player-index players]} game
player (get players player-index)
168. (defn next-move
"Takes the game forward one move by drawing a card for the
current player and moving them accordingly."
[game]
(let [{:keys [decks board player-index players]} game
player (get players player-index)
[card new-decks] (draw-card decks)
169. (defn next-move
"Takes the game forward one move by drawing a card for the
current player and moving them accordingly."
[game]
(let [{:keys [decks board player-index players]} game
player (get players player-index)
[card new-decks] (draw-card decks)
Returns a value, pair of [card updated-decks]
170. (defn next-move
"Takes the game forward one move by drawing a card for the
current player and moving them accordingly."
[game]
(let [{:keys [decks board player-index players]} game
player (get players player-index)
[card new-decks] (draw-card decks)
We destructure and bind the pair as we like
171. (defn next-move
"Takes the game forward one move by drawing a card for the
current player and moving them accordingly."
[game]
(let [{:keys [decks board player-index players]} game
player (get players player-index)
[card new-decks] (draw-card decks)
players-next-location (next-space card board player)]
172. (defn next-move
"Takes the game forward one move by drawing a card for the
current player and moving them accordingly."
[game]
(let [{:keys [decks board player-index players]} game
player (get players player-index)
[card new-decks] (draw-card decks)
players-next-location (next-space card board player)]
(-> game
(assoc :decks new-decks
:player-index (-> player-index inc
(mod (count players))))
173. (defn next-move
"Takes the game forward one move by drawing a card for the
current player and moving them accordingly."
[game]
(let [{:keys [decks board player-index players]} game
player (get players player-index)
[card new-decks] (draw-card decks)
players-next-location (next-space card board player)]
(-> game
(assoc :decks new-decks
:player-index (-> player-index inc
(mod (count players))))
(assoc-in [:players player-index :location]
players-next-location))))
174. (defn next-move
"Takes the game forward one move by drawing a card for the
current player and moving them accordingly."
[game]
(let [{:keys [decks board player-index players]} game
player (get players player-index)
[card new-decks] (draw-card decks)
players-next-location (next-space card board player)]
(-> game
(assoc :decks new-decks
:player-index (-> player-index inc
(mod (count players))))
(assoc-in [:players player-index :location]
players-next-location))))
175. Play the game
(defn play [game]
(->> (iterate next-move game)
(filter game-over?)
first))
178. (defn play [game]
(filter game-over?)
first))
Play the game
(->> (iterate next-move game)
Text
v1
next-move
v2
179. (defn play [game]
(filter game-over?)
first))
Play the game
(->> (iterate next-move game)
Text
v1
next-move
v2 v3
next-move
180. (defn play [game]
(filter game-over?)
first))
Play the game
(->> (iterate next-move game)
Text
v1
next-move
v2 v3
next-move next-move
...
181. (defn play [game]
(filter game-over?)
first))
Play the game
(->> (iterate next-move game)
Text
v1
next-move
v2 v3
next-move next-move
...
(->> 1 (iterate inc) (take 5))
> (1 2 3 4 5)
182. Play the game
(defn play [game]
(->> (iterate next-move game)
(filter game-over?)
first))
(filter game-over?)
first))
Predicate function
183. Play the game
(defn play [game]
(->> (iterate next-move game)
(filter game-over?)
first))
(filter game-over?)
first))
Return the ended game value
184. Play the game
(defn play [game]
(->> (iterate next-move game)
(filter game-over?)
first))
public void play() {
! ! while (nextTurn());
! ! System.out.println(“The winning player is:”
+ players.get(playerPointer).getName());
! }
185. Play the game
(defn play [game]
(->> (iterate next-move game)
(filter game-over?)
first))
(->> (create-game ["Ben" "Maren"])
play
winning-player
:name
(println "The winning player is:"))
public void play() {
! ! while (nextTurn());
! ! System.out.println(“The winning player is:”
+ players.get(playerPointer).getName());
! }
186. Play the game
(defn play [game]
(->> (iterate next-move game)
(filter game-over?)
first))
(->> (create-game ["Ben" "Maren"])
play
winning-player
:name
(println "The winning player is:"))
public void play() {
! ! while (nextTurn());
! ! System.out.println(“The winning player is:”
+ players.get(playerPointer).getName());
! }
Our first side effect!
208. (swap! game-ref add-player "Carter")
Game Reference
(deref game-ref) => current-game-value
(def game-ref (atom (create-game ...)))
(swap! game-ref take-next-move)
Observers can deref to get current state
209. (swap! game-ref add-player "Carter")
Game Reference
(deref game-ref) => current-game-value
(def game-ref (atom (create-game ...)))
(swap! game-ref take-next-move)
@game-ref => current-game-value
Observers can deref to get current state
212. Clojure’s Time Model
State is the current value of an identity.
An identity is series of values over time.
v1 v2 v3
213. Clojure’s Time Model
State is the current value of an identity.
An identity is series of values over time.
A reference to an identity allows updates and
reads to it.
v1 v2 v3
214. Clojure’s Time Model
State is the current value of an identity.
An identity is series of values over time.
A reference to an identity allows updates and
reads to it.
Values never change, the past never changes.
v1 v2 v3
240. (require '[clojure.core.reducers :as r])
(->> (range 100000)
(r/map inc)
(r/reduce +))
(->> (range 100000)
(map inc)
(reduce +))
Process sequences in
parallel with ForkJoin
241. (require '[clojure.core.reducers :as r])
(->> (range 100000)
(r/map inc)
(r/reduce +))
(->> (range 100000)
(map inc)
(reduce +))
Process sequences in
parallel with ForkJoin
The same “what”,
different “how”
242. (extend-protocol CollFold
nil
(coll-fold
[coll n combinef reducef]
(combinef))
Object
(coll-fold
[coll n combinef reducef]
;;can't fold, single reduce
(reduce reducef (combinef) coll))
clojure.lang.IPersistentVector
(coll-fold
[v n combinef reducef]
(foldvec v n combinef reducef))
clojure.lang.PersistentHashMap
(coll-fold
[m n combinef reducef]
(.fold m n combinef reducef fjinvoke fjtask fjfork fjjoin)))
251. Clojure’s Time Model
A reference to an identity allows updates and
reads to it.
Values never change, the past never changes.
v1 v2 v3
State is the current value of an identity.
An identity is series of values over time.
252. Clojure’s Time Model
A reference to an identity allows updates and
reads to it.
Values never change, the past never changes.
v1 v2 v3
State is the current value of an identity.
An identity is series of values over time.Observers can remember the past
253. (defn shadow-ref
"Returns a ref that contains the time - 1 value of the given ref.
In other words, shawdow-ref contains the value of ref before the las
update to it (e.g. swap!). "
[ref]
(let [shadow (atom nil)]
(add-watch ref :shawdower
(fn [_key _ref old-state _new-state]
(reset! shadow old-state)))
shadow))
(def old-game-ref (shadow-ref game-ref))
254. (defn undo-and-skip-card [game-ref old-game-ref]
(let [alternate-reality (-> @old-game-ref
skip-card
take-next-move)]
(reset! game-ref alternate-reality)))
(defn shadow-ref
"Returns a ref that contains the time - 1 value of the given ref.
In other words, shawdow-ref contains the value of ref before the las
update to it (e.g. swap!). "
[ref]
(let [shadow (atom nil)]
(add-watch ref :shawdower
(fn [_key _ref old-state _new-state]
(reset! shadow old-state)))
shadow))
(def old-game-ref (shadow-ref game-ref))
255. Clojure is a functional Lisp
that targets the JVM and
enables simpler software design.
256. How do you want
to spend your
complexity
budget?
259. Tradeoffs
Different way of thinking takes time.
Idiomatic Clojure is slower than idiomatic Java
in micro benchmarks.
260. Tradeoffs
Different way of thinking takes time.
Idiomatic Clojure is slower than idiomatic Java
in micro benchmarks.
Not as much structure provided (e.g. no
familiar class structure), easier to make a mess.
261. Tradeoffs
Different way of thinking takes time.
Idiomatic Clojure is slower than idiomatic Java
in micro benchmarks.
Not as much structure provided (e.g. no
familiar class structure), easier to make a mess.
Tool support. Not many great IDE plugins
conveniently available.
262. Tradeoffs
Different way of thinking takes time.
Idiomatic Clojure is slower than idiomatic Java
in micro benchmarks.
Not as much structure provided (e.g. no
familiar class structure), easier to make a mess.
Tool support. Not many great IDE plugins
conveniently available.
Harder to hire for?
263. Tradeoffs
Different way of thinking takes time.
Idiomatic Clojure is slower than idiomatic Java
in micro benchmarks.
Not as much structure provided (e.g. no
familiar class structure), easier to make a mess.
Tool support. Not many great IDE plugins
conveniently available.
Harder to hire for?
268. C# Async
async void Go() {
_button.IsEnabled = false;
string[] urls = "clojure.org www.albahari.com/nutshell/
golang.org".Split();
int totalLength = 0;
foreach (string url in urls)
{
var uri = new Uri ("http://" + url);
byte[] data = await new WebClient().DownloadDataTaskAsync (uri);
_results.Text += "Length of " + url + " is " + data.Length +
totalLength += data.Length;
}
_results.Text += "Total length: " + totalLength;
}
269. CSP in Go// Run the Web, Image, and Video searches concurrently,
// and wait for all results.
// No locks. No condition variables. No callbacks.
func Google(query string) (results []Result) {
c := make(chan Result)
go func() { c <- Web(query) } ()
go func() { c <- Image(query) } ()
go func() { c <- Video(query) } ()
for i := 0; i < 3; i++ {
result := <-c
results = append(results, result)
}
return
}
// http://talks.golang.org/2012/concurrency.slide#46
270. Go in Clojure
(use 'clojure.core.async)
(defn google [query]
(let [c (chan)]
(go (>! c (<! (web query))))
(go (>! c (<! (image query))))
(go (>! c (<! (video query))))
(go (loop [i 0 ret []]
(if (= i 3)
ret
(recur (inc i) (conj ret (alt! [c t] ([v] v)))))))))
271. “APL is like a beautiful diamond - flawless,
beautifully symmetrical. But you can't add
anything to it. If you try to glue on another
diamond, you don't get a bigger diamond.
Lisp is like a ball of mud. Add more and it's
still a ball of mud - it still looks like Lisp.”
Joel Moses, 1970s