Clojure Through Ruby Coloured Glasses

6539ae86c7d8ddec66f3928beddad569?s=47 Nate Smith
September 11, 2013

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.

6539ae86c7d8ddec66f3928beddad569?s=128

Nate Smith

September 11, 2013
Tweet

Transcript

  1. NATE SMITH @nwjsmith

  2. None
  3. CLOJURE

  4. LISP CONCURRENCY STATE MODEL JAVA INTEROP

  5. LISP CONCURRENCY STATE MODEL JAVA INTEROP NOPE

  6. Y X

  7. CLOJURE RUBY

  8. None
  9. COLLECTION PROCESSING 1 EXPRESSION PROBLEM 2 META PROGRAMMING 3

  10. A BRIEF DETOUR

  11. FUNCTIONAL DYNAMIC JVM

  12. FUNCTIONAL DYNAMIC JS

  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)))
  18. (those parentheses (are (weird)))

  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)))
  21. SYNTAX

  22. PROCESSING COLLECTIONS

  23. None
  24. include Enumerable

  25. #each

  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
  28. THE RIGHT ABSTRACTION OVER COLLECTIONS

  29. #each

  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
  33. SEQUENCE

  34. SEQUENCE

  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
  39. THAT’S GREAT, BUT HOW IS THIS BETTER?

  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)
  42. LAZY

  43. first rest

  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)
  45. THE RIGHT ABSTRACTION OVER COLLECTIONS

  46. THE RIGHT ABSTRACTION OVER COLLECTIONS

  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
  50. ?

  51. EXPRESSION PROBLEM

  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))))
  61. area Circle

  62. area Circle Square

  63. area Circle Square

  64. area perimeter Circle Square

  65. area perimeter Circle Square

  66. area perimeter Circle Square ?

  67. None
  68. MONKEY PATCHING

  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
  71. area perimeter Circle Square

  72. None
  73. PROTOCOLS

  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))))
  78. area perimeter Circle Square

  79. THAT’S GREAT, BUT HOW IS THIS BETTER?

  80. MONKEY PATCHING

  81. MONKEY PATCHING AT

  82. PROTOCOLS

  83. NAME SPACES

  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\"]"
  85. ?

  86. META PROGRAMMING

  87. CODE THAT WRITES CODE

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

    => nil player.name = "Phil Kessel" player.name # => "Phil Kessel"
  90. class Player def name @name end def name=(name) @name =

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

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

  93. class Class def attribute_accessor(attribute) end end

  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"}
  103. MACROS

  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)))
  108. THAT’S GREAT, BUT HOW IS THIS BETTER?

  109. CODE THAT WRITES CODE

  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
  113. FOR REALZ

  114. unless 1 > 2 "this will be returned" else "this

    won't" end
  115. def unless(test, then_clause, else_clause) end

  116. (unless (> 1 2) "this will be returned" "this won't")

  117. (defmacro unless [test-exp then else] `(if (not ~test-exp) ~then ~else))

  118. LISP IS A PROGRAMMABLE PROGRAMMING LANGUAGE JOHN FODERADO

  119. ?

  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
  121. THANK YOU