Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Clojure Through Ruby Coloured Glasses

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.

Nate Smith

September 11, 2013
Tweet

More Decks by Nate Smith

Other Decks in Programming

Transcript

  1. Y X

  2. 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)
  3. ; 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}
  4. 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)))
  5. 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
  6. 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
  7. [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
  8. ; 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)
  9. 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
  10. (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
  11. (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
  12. ; 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)
  13. (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)
  14. IT IS BETTER TO HAVE 100 FUNCTIONS OPERATE ON 1

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

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

    ABSTRACTION THAN 10 FUNCTIONS ON 10 ABSTRACTIONS
  17. ?

  18. (defrecord Circle [radius]) (defn area "Calculate the area of a

    shape." [shape] (* Math/PI (* (:radius shape) (:radius shape))))
  19. 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
  20. (defrecord Circle [radius]) (defn area "Calculate the area of a

    shape." [shape] (* Math/PI (* (:radius shape) (:radius shape))))
  21. (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))))
  22. 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
  23. (defrecord Circle [radius]) (defn area "Calculate the area of a

    shape." [shape] (* Math/PI (* (:radius shape) (:radius shape))))
  24. (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))))
  25. 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
  26. class Circle def perimeter 2 * Math::PI * @radius end

    end class Square def perimeter @side * 4 end end
  27. (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))))
  28. (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))))
  29. (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))))
  30. (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))))
  31. (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\"]"
  32. ?

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

    => nil player.name = "Phil Kessel" player.name # => "Phil Kessel"
  34. class Player attr_accessor :name end player = Player.new player.name #

    => nil player.name = "Phil Kessel" player.name # => "Phil Kessel"
  35. 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
  36. class Player attribute_accessor :name end player = Player.new player.name #

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

    => nil player.name = "Phil Kessel" player.name # => "Phil Kessel"
  38. (attribute-accessor :name) (get-name player) ; => "Phil Kessel" (set-name player

    "Joffrey Lupul") ; => #Player{:name "Joffrey Lupul"}
  39. (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#)))))
  40. (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#)))))
  41. (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#)))))
  42. (attribute-accessor :name) (do (defn get-name [record] (get record :name)) (defn

    set-name [record value] (assoc record :name value)))
  43. 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
  44. ?

  45. 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