Nate Smith
September 11, 2013
160

# Clojure Through Ruby Coloured Glasses

A talk I gave at Toronto Ruby contrasting the approaches Ruby and Clojure take to collection processing, Metaprogramming, and solving the Expression Problem.

## Nate Smith

September 11, 2013

## Transcript

2. None

8. None

13. ### 12345678987654 ; Arbitrary precision integers 1.234 ; Doubles 1.234M ;

BigDecimals 22/7 ; Rational numbers "chunky bacon" ; Strings chunky bacon ; Symbols :chunky :bacon ; Keywords true false ; Boolean nil ; Null #"^chunky\s+bacon\$" ; Regular expressions \A \b \u2603 ; Characters (UTF-16)
14. ### ; Lists - singly-linked, grow at front (1 2 3

4 5) (chunky bacon) (list 1 2 3) ; Vectors - indexed, grow at end [1 2 3 4 5] [chunky bacon] ; Maps - key/value lookup {:chunky "bacon", :foo "bar"} {:one 1 :two 2} ; Sets #{:chunky :bacon}
15. ### (println "Hello, World!") ; Hello, World! ; => nil (println

(+ 1 2 3)) ; 6 ; => nil
16. ### (defn hello "Print a greeting." [first-name last-name] (let [full (str

first-name " " last-name)] (println full)))
17. ### def hello(first_name, last_name) full = "#{first_name} #{last_name}" puts full end

(defn hello "Print a greeting." [first-name last-name] (let [full (str first-name " " last-name)] (println full)))

19. ### (defn hello "Print a greeting." [first-name last-name] (let [full (str

first-name " " last-name)] (println full)))
20. ### (defn hello "Print a greeting." [first-name last-name] (let [full (str

first-name " " last-name)] (println full)))

23. None

26. ### File.open("/etc/hosts").each do |line| puts line end [1, 2, 3].each {

|n| puts n + 1 }
27. ### all? any? chunk collect collect_concat count cycle detect drop drop_while

each_cons each_entry each_slice each_with_index each_with_object entries find find_all find_index first flat_map grep group_by include? inject lazy map max max_by member? min min_by minmax minmax_by none? one? partition reduce reject reverse_each select slice_before sort sort_by take take_while to_a to_set zip

30. ### all? any? chunk collect collect_concat count cycle detect drop drop_while

each_cons each_entry each_slice each_with_index each_with_object entries find find_all find_index first flat_map grep group_by include? inject lazy map max max_by member? min min_by minmax minmax_by none? one? partition reduce reject reverse_each select slice_before sort sort_by take take_while to_a to_set zip
31. ### [1, 2, 3, 4, 5].drop(2) # => [3, 4, 5]

(0..99).inject(&:+) # => 4950 [1, 2, 3, 4].map { |n| n += 1 } # => [2, 3, 4, 5] {one: 1, two: 2, three: 3}.all? { |k, v| v < 10 } # => true Set.new([3, 5, 7, 8, 9, 10]).any? { |n| n.even? } # => true
32. None

35. ### ; if collection is not empty return ; a seq

object, otherwise return nil (seq coll) ; return the first item in the ; seq, or nil if there are no items (first seq) ; return a seq representing the rest ; of the sequence (rest seq) ; return a seq where item is the ; first element and seq the rest (cons item seq)
36. ### apply butlast concat cons cycle distinct doall dorun doseq drop

drop-last drop-while empty? every? ffirst filter first flatten fnext for frequencies group-by interleave interpose into into-array keep keep-indexed last lazy-cat map map-indexed mapcat next nfirst nnext not-any? not-empty not-every? nth nthnext partition partition-all partition-by pmap rand-nth realized? reduce reductions remove replace rest reverse second seq? seque set shuffle some sort sort-by split-at split-with take take-nth take-while to-array-2d vec when-first zipmap
37. ### (drop 2 [1 2 3 4 5]) ; => (3

4 5) [1, 2, 3, 4, 5].drop(2) # => [3, 4, 5] (reduce + (range 100)) ; => 4950 (0..99).inject(&:+) # => 4950 (map inc [1 2 3 4]) ; => (2 3 4 5) [1, 2, 3, 4].map { |n| n += 1 } # => [2, 3, 4, 5] (every? (fn [[k v]] (< v 10)) {:one 1 :two 2 :three 3}) ; => true {one: 1, two: 2, three: 3}.all? { |k, v| v < 10 } # => true (some even? #{3 5 7 8 9 10}) ; => true Set.new([3, 5, 7, 8, 9, 10]).any? { |n| n.even? } # => true
38. ### (drop 2 [1 2 3 4 5]) ; => (3

4 5) [1, 2, 3, 4, 5].drop(2) # => [3, 4, 5] (reduce + (range 100)) ; => 4950 (0..99).inject(&:+) # => 4950 (map inc [1 2 3 4]) ; => (2 3 4 5) [1, 2, 3, 4].map { |n| n += 1 } # => [2, 3, 4, 5] (every? (fn [[k v]] (< v 10)) {:one 1 :two 2 :three 3}) ; => true {one: 1, two: 2, three: 3}.all? { |k, v| v < 10 } # => true (some even? #{3 5 7 8 9 10}) ; => true Set.new([3, 5, 7, 8, 9, 10]).any? { |n| n.even? } # => true

40. ### (take 10 (cycle [1 2 3 4])) ; => (1

2 3 4 1 2 3 4 1 2)
41. ### ; if collection is not empty return ; a seq

object, otherwise return nil (seq coll) ; return the first item in the ; seq, or nil if there are no items (first seq) ; return a seq representing the rest ; of the sequence (rest seq) ; return a seq where item is the ; first element and seq the rest (cons item seq)

44. ### (take 10 (cycle [1 2 3 4])) ; => (1

2 3 4 1 2 3 4 1 2) (take 10 (repeatedly (fn [] (rand-int 9)))) ; => (4 6 5 3 4 0 6 3 2) (take 10 (map inc (cycle [1 2 3 4]))) ; => (2 3 4 5 2 3 4 5 2 3)

47. ### IT IS BETTER TO HAVE 100 FUNCTIONS OPERATE ON 1

DATA STRUCTURE THAN 10 FUNCTIONS ON 10 DATA STRUCTURES
48. ### IT IS BETTER TO HAVE 100 FUNCTIONS OPERATE ON 1

DATA STRUCTURE THAN 10 FUNCTIONS ON 10 DATA STRUCTURES
49. ### IT IS BETTER TO HAVE 100 FUNCTIONS OPERATE ON 1

ABSTRACTION THAN 10 FUNCTIONS ON 10 ABSTRACTIONS

52. ### class Circle def initialize(radius) @radius = radius end def area

Math::PI * (@radius ** 2) end end
53. ### (defrecord Circle [radius]) (defn area "Calculate the area of a

shape." [shape] (* Math/PI (* (:radius shape) (:radius shape))))
54. ### class Circle def initialize(radius) @radius = radius end def area

Math::PI * (@radius ** 2) end end
55. ### class Circle def initialize(radius) @radius = radius end def area

Math::PI * (@radius ** 2) end end class Square def initialize(side) @side = side end def area @side ** 2 end end
56. ### (defrecord Circle [radius]) (defn area "Calculate the area of a

shape." [shape] (* Math/PI (* (:radius shape) (:radius shape))))
57. ### (defrecord Circle [radius]) (defrecord Square [side]) (defn area "Calculate the

area of a shape." [shape] (if (= Circle (class shape)) (* Math/PI (* (:radius shape) (:radius shape))) (* (:side shape) (:side shape))))
58. ### class Circle def initialize(radius) @radius = radius end def area

Math::PI * (@radius ** 2) end end class Square def initialize(side) @side = side end def area @side ** 2 end end
59. ### (defrecord Circle [radius]) (defn area "Calculate the area of a

shape." [shape] (* Math/PI (* (:radius shape) (:radius shape))))
60. ### (defrecord Circle [radius]) (defn area "Calculate the area of a

shape." [shape] (* Math/PI (* (:radius shape) (:radius shape)))) (defn perimeter "Calculate the perimeter of a shape." [shape] (* Math/PI (* 2 (:radius shape))))

67. None

69. ### class Circle def initialize(radius) @radius = radius end def area

Math::PI * (@radius ** 2) end end class Square def initialize(side) @side = side end def area @side ** 2 end end
70. ### class Circle def perimeter 2 * Math::PI * @radius end

end class Square def perimeter @side * 4 end end

72. None

74. ### (defrecord Circle [radius]) (defrecord Square [side]) (defprotocol Shape "A shape."

(area [shape] "Calculate the area of the shape") (perimeter [shape] "Calculate the perimeter of the shape")) (extend-type Circle Shape (area [c] (* Math/PI (* (:radius c) (:radius c)))) (perimeter [c] (* 2 Math/PI (:radius c)))) (extend-type Square Shape (area [s] (* (:side s) (:side s))) (perimeter [s] (* 4 (:side s))))
75. ### (defrecord Circle [radius]) (defrecord Square [side]) (defprotocol Shape "A shape."

(area [shape] "Calculate the area of the shape") (perimeter [shape] "Calculate the perimeter of the shape")) (extend-type Circle Shape (area [c] (* Math/PI (* (:radius c) (:radius c)))) (perimeter [c] (* 2 Math/PI (:radius c)))) (extend-type Square Shape (area [s] (* (:side s) (:side s))) (perimeter [s] (* 4 (:side s))))
76. ### (defrecord Circle [radius]) (defrecord Square [side]) (defprotocol Shape "A shape."

(area [shape] "Calculate the area of the shape") (perimeter [shape] "Calculate the perimeter of the shape")) (extend-type Circle Shape (area [c] (* Math/PI (* (:radius c) (:radius c)))) (perimeter [c] (* 2 Math/PI (:radius c)))) (extend-type Square Shape (area [s] (* (:side s) (:side s))) (perimeter [s] (* 4 (:side s))))
77. ### (defrecord Circle [radius]) (defrecord Square [side]) (defprotocol Shape "A shape."

(area [shape] "Calculate the area of the shape") (perimeter [shape] "Calculate the perimeter of the shape")) (extend-type Circle Shape (area [c] (* Math/PI (* (:radius c) (:radius c)))) (perimeter [c] (* 2 Math/PI (:radius c)))) (extend-type Square Shape (area [s] (* (:side s) (:side s))) (perimeter [s] (* 4 (:side s))))

84. ### (ns janky.json (:require [clojure.string :as string])) (defprotocol JSON (to-json [value]

"Convert value to JSON string")) (extend-protocol JSON java.lang.Number (to-json [n] (str n)) java.lang.CharSequence (to-json [cs] (str \" cs \")) clojure.lang.Named (to-json [named] (str \" (name named) \")) java.util.Collection (to-json [coll] (str \[ (string/join \, (map to-json coll)) \]))) (to-json [:hello "goodbye" 1 3.21 #{1 2} 'foo]) ; => "[\"hello\",\"goodbye\",1,3.21,[1,2],\"foo\"]"

88. None
89. ### class Player attr_accessor :name end player = Player.new player.name #

=> nil player.name = "Phil Kessel" player.name # => "Phil Kessel"

name end end
91. ### class Player attr_accessor :name end player = Player.new player.name #

=> nil player.name = "Phil Kessel" player.name # => "Phil Kessel"

94. ### class Class def attribute_accessor(attribute) define_method attribute.to_s do instance_variable_get("@#{attribute}") end define_method

"#{attribute}=" do |value| instance_variable_set "@#{attribute}", value end end end
95. ### class Class def attribute_accessor(attribute) define_method attribute.to_s do instance_variable_get("@#{attribute}") end define_method

"#{attribute}=" do |value| instance_variable_set "@#{attribute}", value end nil end end
96. ### class Player attribute_accessor :name end player = Player.new player.name #

=> nil player.name = "Phil Kessel" player.name # => "Phil Kessel"
97. ### class Player attribute_accessor :name end player = Player.new player.name #

=> nil player.name = "Phil Kessel" player.name # => "Phil Kessel"
98. None
99. ### (defrecord Player [name]) (def player (->Player nil)) (:name player) ;

=> nil (assoc player :name "Phil Kessel")
100. ### (attribute-accessor :name) (defn get-name [record] (:name player)) (defn set-name [record

value] (assoc player :name "Phil Kessel"))
101. ### (attribute-accessor :name) (defn get-name [record] (:name player)) (defn set-name [record

value] (assoc player :name value))
102. ### (attribute-accessor :name) (get-name player) ; => "Phil Kessel" (set-name player

"Joffrey Lupul") ; => #Player{:name "Joffrey Lupul"}

104. ### (attribute-accessor :name) (defmacro attribute-accessor [attr-name] (let [n (name attr-name) reader

(symbol (str "get-" n)) writer (symbol (str "set-" n))] `(do (defn ~reader [record#] (get record# ~attr-name)) (defn ~writer [record# value#] (assoc record# ~attr-name value#)))))
105. ### (attribute-accessor :name) (defmacro attribute-accessor [attr-name] (let [n (name attr-name) reader

(symbol (str "get-" n)) writer (symbol (str "set-" n))] `(do (defn ~reader [record#] (get record# ~attr-name)) (defn ~writer [record# value#] (assoc record# ~attr-name value#)))))
106. ### (attribute-accessor :name) (defmacro attribute-accessor [attr-name] (let [n (name attr-name) reader

(symbol (str "get-" n)) writer (symbol (str "set-" n))] `(do (defn ~reader [record#] (get record# ~attr-name)) (defn ~writer [record# value#] (assoc record# ~attr-name value#)))))
107. ### (attribute-accessor :name) (do (defn get-name [record] (get record :name)) (defn

set-name [record value] (assoc record :name value)))

110. None
111. ### class Class def attribute_accessor(attribute) define_method attribute.to_s do instance_variable_get("@#{attribute}") end define_method

"#{attribute}=" do |value| instance_variable_set "@#{attribute}", value end nil end end
112. None

won't" end

120. ### SOME MAY SAY RUBY IS A BAD RIP-OFF OF LISP

OR SMALLTALK, AND I ADMIT THAT. BUT IT IS NICER TO ORDINARY PEOPLE. MATZ