Skip to content

Instantly share code, notes, and snippets.

@brandonbloom
Created January 15, 2014 02:58
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save brandonbloom/8429988 to your computer and use it in GitHub Desktop.
Save brandonbloom/8429988 to your computer and use it in GitHub Desktop.
;;;; 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