Created
January 15, 2014 02:58
-
-
Save brandonbloom/8429988 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;;; Super top secret talk stuff nobody should ever see. Shhh. | |
(in-ns 'user) | |
(defmacro bench [& body] | |
`((re-find #"\"(.*)\"" (with-out-str (time (do ~@body)))) 1)) | |
*ns* | |
(require 'clojure.walk) | |
(refer 'clojure.walk :only '[macroexpand-all]) | |
(gensym) | |
;;;; Why Clojure? -- Brandon Bloom | |
;;;; Or if not Clojure, what can you learn from it? | |
;; Going to cover things that are both unique to Clojure | |
;; and directly applicable to your work in other languages. | |
;;; Basic Syntax | |
;; Numerics | |
42, 2r101010 ; long integers | |
3.14 ; double floats | |
2/5 ; ratios | |
;; Units | |
(comment ; LT bug | |
false, true ; booleans | |
nil ; null, logical false | |
) | |
;; Strings and Things | |
"hello" ; strings | |
'foo, 'my/foo ; symbols (quoted) | |
:bar, :my/bar ; keywords | |
\x, \space ; characters | |
#"a*b+" ; regexps | |
;; Composites | |
[5 10 15] ; vectors | |
'(1 2 3) ; lists (quoted) | |
{:key "value"} ; maps | |
#{:x :y :z} ; sets | |
;;; Evaluation | |
;; Most scalar values are self-evaluating | |
10, "hello world" | |
;; But symbols are resolved | |
inc | |
;; Keywords are like symbols that resolve to themselves | |
:foo | |
;; Lists evaluate as invocations | |
(inc 10) | |
;; List heads can control evaluation | |
(def ten 10) ; ten not resolved | |
(inc ten) ; ten is resolved | |
;; Composites evaluate their elements | |
[5 ten 15] | |
{:ten ten} | |
;; Quoting prevents evaluation | |
'[5 ten 15] | |
'{:ten ten} | |
;; To be clear: In a Lisp, eval is polymorphic and operates on | |
;; data directly, not code strings or restricted to AST values. | |
(eval 10) | |
(eval "(inc 10)") | |
(eval (inc ten)) | |
(eval '(inc ten)) | |
(eval ''(inc ten)) | |
;;; Interactive! | |
;;; Clojure programmers *live* in the REPL | |
;; Namespaces are first-class | |
*ns* | |
;; Top-level definitions are first-class (Vars) | |
#'inc, #'clojure.core/inc | |
#'ten, #'user/ten | |
(class #'ten) | |
(deref #'inc) | |
(deref #'ten) | |
@#'ten | |
;; The REPL has a current namespace, and you can move around. | |
(in-ns 'why) | |
clojure.core/*ns* | |
#'ten ; whoops! | |
#'user/ten | |
;; Vars can be brought in to scope | |
(clojure.core/use 'clojure.core) | |
*ns* | |
(refer 'user :only '[ten]) | |
ten | |
;; Namespaces can be aliased | |
(alias 'u 'user) | |
u/ten | |
;; Let's go back... | |
(in-ns 'user) | |
*ns* | |
;; There are tools to reflect on all this too. | |
;; eg One of the many ns- functions: | |
(ns-publics *ns*) | |
;;; Functional | |
;; Higher order functions | |
(map inc [5 10 15]) | |
;; Named function | |
(defn double [x] | |
(* x 2)) | |
(double 5) | |
;; Anonymous functions | |
(map (fn [x] (inc (double x))) [5 10 15]) | |
;; Shorthand syntax | |
(map #(inc (double %)) [5 10 15]) | |
;; Functions that make functions | |
(def ment (juxt dec inc)) | |
(ment 0) | |
(map ment [5 10 15]) | |
((fnil inc 10) nil) | |
;; More traditional function composition | |
(def double-and-one (comp inc double)) | |
(double-and-one 5) | |
(def bicr (partial + 2)) | |
(bicr 5) | |
(+) | |
(*) | |
;; Functions may be variadic | |
(+ 3 5 7) | |
(apply + [3 5 7]) | |
(apply * [3 5 7]) | |
(apply * []) | |
(<= -1 0 1) | |
(< -1 5 1) | |
;; Associative data structures are callable | |
({:x 5} :x) | |
(["a" "b" "c"] 1) | |
;; Keywords are callable too! | |
(:x {:x 5}) | |
;;; Values! | |
;; Immutable & Persistent | |
(def v1 [5 10 15]) | |
(def v2 (conj v1 20)) | |
v1 ; unchanged! | |
v2 | |
(def m1 {:foo 1}) | |
(def m2 (assoc m1 :bar 2)) | |
m1 ; also unchanged | |
m2 | |
;; Utilizes structural sharing, not naive copying | |
;; Fast & memory efficient! "changes" are (log32 N) path copying | |
(bench (def big-vector (vec (range 1000000)))) | |
(bench (conj big-vector :foo)) | |
;; Equality is fast too. Pointer equality mean asymptotic (log32 N) comparisons | |
(bench (= (conj big-vector :x) big-vector)) | |
(bench (= (conj big-vector :x) (conj big-vector :y))) | |
;; Universal suitability for map keys | |
(def m3 {{:complex "key"} 1}) | |
(m3 {:complex "key"}) | |
;; They are also suitable for key _paths_ | |
(def m4 {:x {:y {:z 2}}}) | |
(update-in m4 [:x :y :z] + 2) | |
m4 | |
[] | |
[(+ 1 1)] | |
(update-in {1 2, 2 3} [(+ 1 1)] * 10) | |
;; Don't need to worry about "deep" or "shallow" clones | |
(def nested {:map {:in "a map"}}) | |
(assoc-in nested [:map :in] "another map") | |
nested | |
;; Aliasing is completely safe (and cheap) | |
;; Ruby people: Has options.reverse_merge!(defaults) bitten you? | |
;; JavaScript people: Sick of _.extend({}, defaults, options) nonsense? | |
;; Python people: Aren't you happy **kwargs does a clone for you? | |
;; ...but what happens when you want to merge defaults in to a nested value? | |
;; Not an issue with immutable values! | |
;;; reductions | |
;; Reduce lets us calculate aggregates of collections | |
(reduce + 0 [5 10 15]) | |
(reductions + 0 [5 10 15]) | |
;; Let's re-implement clojure.core/frequencies using reduce | |
(def v3 [:a :a :b :a :c :a :c :c :c :c :b :d :e :e :b :f]) | |
(frequencies v3) | |
(reduce (fn [acc x] | |
(update-in acc [x] (fnil inc 0))) | |
{} | |
v3) | |
;; Debug with reductions! | |
(reductions (fn [acc x] | |
(update-in acc [x] (fnil inc 0))) | |
{} | |
v3) | |
;; I've written entire interpreters this way, then debugged them: | |
;; (drop 100 (reductions step init program)) | |
;;; Some more building blocks | |
;; Locals | |
(let [x 5] | |
(inc x)) | |
;; Multiple bindings at a time | |
(let [x 5 | |
y 10] | |
(+ x y)) | |
;; Sequence destructuring | |
v1 | |
(let [[x y z] v1] | |
y) | |
;; "Rest" destructuring | |
(let [[x & more] v1] | |
more) | |
;; Map destructuring | |
m2 | |
(let [{x :foo} m2] | |
x) | |
;; Keyword destructuring shorthand | |
(let [{:keys [foo bar]} m2] | |
foo) | |
;; We got this far with out ifs? | |
(if true 1 2) | |
(if [] 1 2) | |
(if 0 1 2) | |
(if 9 1 2) | |
;; Only two false values! | |
(if false 1 2) | |
(if nil 1 2) | |
;; Threading macros | |
(-> 5 inc (- 2)) ; kinda like (5).inc().sub(2) | |
(->> 5 inc (- 2)) | |
(macroexpand-all '(-> 5 inc (- 2))) | |
(macroexpand-all '(->> 5 inc (- 2))) | |
;; We've seen some macros already, not gonna talk about how to | |
;; create your own, since you really don't need to do it often. | |
;; Besides, that wouldn't help you non-Lisp folks :-P | |
;;; Speculation | |
(def cart {:items [{:sku :milk, :price 4.50} | |
{:sku :butter, :price 3.00} | |
{:sku :cheese, :price 3.25}]}) | |
;; No need to bother with a Cart class, since immutability | |
;; dratmatically reduces the value proposition of encapsulation. | |
;; Invariants are maintained by construction! | |
(defn total [{:keys [items] :as cart}] | |
(assoc cart :total (apply + (map :price items)))) | |
(total cart) | |
(-> cart total :total) | |
(defn dollar-off [cart] | |
(-> cart | |
total | |
(update-in [:total] - 1))) | |
(dollar-off cart) | |
(defn discount-milk [cart] | |
(-> cart | |
(update-in [:items] | |
(fn [items] | |
(map (fn [{:keys [sku] :as item}] | |
(if (= sku :milk) | |
(update-in item [:price] * 0.9) | |
item)) | |
items))) | |
total)) | |
(discount-milk cart) | |
cart | |
(defn best-coupon [cart coupons] | |
(apply min-key | |
#(-> cart % :total) | |
coupons)) | |
(str (best-coupon cart #{dollar-off discount-milk})) | |
((best-coupon cart #{dollar-off discount-milk}) cart) | |
;; You can do this sort of thing in your language with custom immutable | |
;; classes. If you need immutable sequences or maps, you can get purely | |
;; functional data structures for your language, see this question: | |
;; http://stackoverflow.com/questions/20834721/what-libraries-provide-persistent-data-structures | |
;;; Abstractions | |
;; Core data structures are abstractions | |
(map? {}) | |
(map? []) | |
(associative? {}) | |
(associative? []) | |
;; Abstractions may have multiple concrete implementations | |
(class (array-map :a 1)) | |
(class (hash-map :a 1)) | |
;; Even the syntax is abstract | |
(class {:a 1 :b 2 :c 3 :d 4 :e 5}) | |
(class {:a 1 :b 2 :c 3 :d 4 :e 5 | |
:f 6 :g 7 :h 8 :i 9 :j 10}) | |
;; Core functions are highly-polymorphic | |
(conj [1 2 3] 2) | |
(conj #{1 2 3} 2) | |
(conj (list 1 2 3) 2) | |
(conj {:x 1 :y 2} [:z 3]) | |
;; Many functions internally coerce to sequences | |
(seq [1 2 3]) | |
(seq #{1 2 3}) | |
(take 5 (range 1000000000000)) | |
(seq {:x 1 :y 2 :z 3}) | |
;; For example | |
(take 2 [1 2 3]) | |
(take 2 #{1 2 3}) | |
(take 2 {:x 1 :y 2 :z 3}) | |
;;; Polymorphism a la carte | |
(def zoo [{:animal :cow, :size :large} | |
{:animal :dog, :size :small} | |
{:animal :dog, :size :large} | |
{:animal :cat, :size :small}]) | |
(defmulti sound :animal) | |
(deref #'sound) | |
(defmethod sound :cow [_] "moo") | |
(defmethod sound :dog [{:keys [size]}] | |
(if (= size :large) | |
"WOOF" | |
"yip")) | |
(defmethod sound :cat [_] "meow") | |
(map sound zoo) | |
;; Orthogonal dispatch | |
(defmulti heavy? :size) | |
(defmethod heavy? :large [_] true) | |
(defmethod heavy? :small [_] false) | |
(map heavy? zoo) | |
;; And because I love juxt... | |
(map (juxt identity sound heavy?) zoo) | |
;; Lots more to say about multimethods, but not now: | |
;; hierarchies, defaults, preferences, etc. | |
;;; Create your own abstractions | |
;; Use concrete, unencapsulated representations when possible! | |
;; If you genuinely need new abstractions, you can still get | |
;; the benefit of the 90% case: polymorphism dispatched by type. | |
;; Faster than multimethods, interops nicely with host interfaces. | |
(defprotocol Frobable | |
(frob [this])) | |
;; And create concrete instances of them | |
(deftype Door [open?] | |
Frobable | |
(frob [_] | |
(Door. (not open?)))) | |
(class Door) | |
(new Door false) ; opaque... | |
(Door. false) ; short hand | |
(.open? (Door. false)) ; well kinda opaque... | |
;; It's frobable! | |
(.open? (frob (Door. false))) | |
;; But transparency is better, | |
;; and most "business objects" are associative abstractions: | |
(defrecord Window [open?] | |
Frobable | |
(frob [window] | |
(update-in window [:open?] not))) | |
(Window. true) | |
(assoc (Window. true) :panes 2) | |
(frob (Window. true)) | |
;; You can extend new abstractions to old types | |
Frobable | |
(extend-protocol Frobable | |
java.lang.String | |
(frob [string] | |
(.toUpperCase string))) | |
(frob "a string") | |
;; And test for participation in an abstraction | |
(defn frobable? [x] | |
(satisfies? Frobable x)) | |
(map frobable? [(Door. true) "a string"]) | |
(map frobable? [:we-did-not 'extend-to-these]) | |
;;; Identities | |
; Epochal Time Model: https://i.imgur.com/S9j2SJx.png | |
; http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey | |
(def n (atom 0)) | |
;; Current value | |
(deref n) | |
@n ; shorthand | |
;; Advancing time | |
(reset! n 5) | |
(swap! n inc) | |
n | |
(swap! n * 2) | |
@n | |
;;; The world in an atom | |
(def init-game {:player {:position [5 10] :velocity [2 1]} | |
:monsters [{:position [2 5] :velocity [-1 -1]} | |
{:position [5 6] :velocity [3 2]}]}) | |
(def game (atom init-game)) | |
(defn entity-physics [{:keys [velocity] :as entity}] | |
(update-in entity [:position] #(mapv + % velocity))) | |
(-> init-game :player entity-physics :position) | |
(defn game-physics [game] | |
(-> game | |
(update-in [:player] entity-physics) | |
(update-in [:monsters] #(mapv entity-physics %)))) | |
(-> (game-physics init-game) :monsters) | |
init-game | |
(swap! game game-physics) | |
(reset! game init-game) | |
(get-in @game [:monsters 1 :position]) | |
;; Debug with iterate! | |
(take 10 (iterate #(* % 2) 5)) | |
(->> init-game | |
(iterate game-physics) | |
(map #(get-in % [:player :position])) | |
(take 5)) | |
;;; Lots more to say about idenities & reference types. | |
;; These are also *great* for concurrency, but I won't cover | |
;; that since Clojure's concurrency primitives are discussed | |
;; in great detail across the web: refs, agents, delays, etc. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment