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

A Rubyist's Guide to Functional Programming with Clojure

A Rubyist's Guide to Functional Programming with Clojure

Ruby is such an elegant language! There couldn't be anything for us to learn from Clojure...could there?

Functional programming seems to be all the rage these days. The Clojure programming language is getting a lot of attention, too, and not without reason. Clojure lets you get stuff done in a concise way.

You'll learn:

* Some basic Clojure syntax

* What makes functional programming different from object-oriented programming

* What higher-order functions are, and how we can use them to create powerful abstractions

* Why concurrency is easier and safer in Clojure than in Ruby

We'll wrap up with some lessons we can apply to our own Ruby code. Learning Clojure will make you a better Ruby programmer.

Michael Stalker

September 08, 2015
Tweet

More Decks by Michael Stalker

Other Decks in Technology

Transcript

  1. Let’s talk about Clojure ◦ What? ◦ Why? ◦ How

    do I get started? ◦ How does this help my Ruby?
  2. “ Why did I write yet another programming language? Basically

    because I wanted: ◦ A Lisp ◦ for Functional Programming ◦ symbiotic with an established Platform ◦ designed for Concurrency and couldn't find one. - Rich Hickey
  3. IT’S A LISP DIALECT ; Add 1 plus 3 (+

    1 3) ;= 4 (+ 1 2 3 4) ;= 10
  4. CREATING A FUNCTION (def double (fn [x] (* 2 x))

    double 3 ;= #<user$double user$double@b3f0ef0> ;= 3
  5. CREATING A FUNCTION (def double (fn [x] (* 2 x))

    double 3 ;= #<user$double user$double@b3f0ef0> ;= 3
  6. BIGGER FUNCTIONS (defn take [n coll] (lazy-seq (when (pos? n)

    (when-let [s (seq coll)] (cons (first s) (take (dec n) (rest s)))))))
  7. WHAT IS CLOJURE? 1. It’s a programming language. 2. It’s

    a Lisp dialect. 3. It’s functional.
  8. FUNCTIONS ARE FIRST-CLASS OBJECTS ◦ Functions are data, just like

    the string “hi” and the number 3. ◦ You can pass functions as arguments to functions.
  9. FUNCTIONS ARE DATA (defn add-and-double [x y] (* 2 (+

    x y))) (defn combine-and-triple [combiner a b] (* 3 (combiner a b)))
  10. FUNCTIONS ARE DATA (defn add-and-double [x y] (* 2 (+

    x y))) (defn combine-and-triple [combiner a b] (* 3 (combiner a b))) (combine-and-triple add-and-double 4 6)
  11. FUNCTIONS ARE DATA (defn add-and-double [x y] (* 2 (+

    x y))) (defn combine-and-triple [combiner a b] (* 3 (combiner a b))) (combine-and-triple add-and-double 4 6)
  12. FUNCTIONS ARE DATA (defn add-and-double [x y] (* 2 (+

    x y))) (defn combine-and-triple [combiner a b] (* 3 (combiner a b))) ;= (* 3 (add-and-double 4 6))
  13. FUNCTIONS ARE DATA (defn add-and-double [x y] (* 2 (+

    x y))) (defn combine-and-triple [combiner a b] (* 3 (combiner a b))) ;= (* 3 (add-and-double 4 6)) ;= (* 3 (* 2 (+ 4 6)))
  14. FUNCTIONS ARE DATA (defn add-and-double [x y] (* 2 (+

    x y))) (defn combine-and-triple [combiner a b] (* 3 (combiner a b))) ;= (* 3 (add-and-double 4 6)) ;= (* 3 (* 2 (+ 4 6))) ;= (* 3 (* 2 10))
  15. FUNCTIONS ARE DATA (defn add-and-double [x y] (* 2 (+

    x y))) (defn combine-and-triple [combiner a b] (* 3 (combiner a b))) ;= (* 3 (add-and-double 4 6)) ;= (* 3 (* 2 (+ 4 6))) ;= (* 3 (* 2 10)) -> 60
  16. BLOCKS IN RUBY def combine_and_triple(a, b) 3 * yield(a, b)

    end combine_and_triple(4, 6) do |x, y| 2 * (x + y) end
  17. BLOCKS IN RUBY def combine_and_triple(a, b) 3 * yield(a, b)

    end combine_and_triple(4, 6) do |x, y| 2 * (x + y) end #=> 60
  18. LAMBDAS IN RUBY def combine_and_triple(a, b) 3 * yield(a, b)

    end add_and_double = ->(x, y) { 2 * (x + y) }
  19. LAMBDAS IN RUBY def combine_and_triple(a, b) 3 * yield(a, b)

    end add_and_double = ->(x, y) { 2 * (x + y) } combine_and_triple(4, 6, &add_and_double)
  20. LAMBDAS IN RUBY def combine_and_triple(a, b) 3 * yield(a, b)

    end add_and_double = ->(x, y) { 2 * (x + y) } combine_and_triple(4, 6, &add_and_double) #=> 60
  21. LAMBDAS IN RUBY def combine_and_triple(a, b, combiner) 3 * combiner.call(a,

    b) end add_and_double = ->(x, y) { 2 * (x + y) } combine_and_triple(4, 6, add_and_double)
  22. LAMBDAS IN RUBY def combine_and_triple(a, b, combiner) 3 * combiner.call(a,

    b) end add_and_double = ->(x, y) { 2 * (x + y) } combine_and_triple(4, 6, add_and_double) #=> 60
  23. SPECIFY BEHAVIOR THROUGH INHERITANCE class NumberProcessor def combine_and_triple(a, b) raise

    NotImplementedError end end class AdderDoubler < NumberProcessor def combine_and_triple(a, b) 3 * (2 * (a + b)) end end
  24. SPECIFY BEHAVIOR THROUGH INHERITANCE class NumberProcessor def combine_and_triple(a, b) raise

    NotImplementedError end end class AdderDoubler < NumberProcessor def combine_and_triple(a, b) 3 * (2 * (a + b)) end end AdderDoubler.new.combine_and_triple(4, 6)
  25. SPECIFY BEHAVIOR THROUGH COMPOSITION class NumberProcessor def combine_and_triple(a, b, combiner)

    3 * combiner.process(a, b) end end class AdderDoubler def process(a, b) 2 * (a + b) end end
  26. SPECIFY BEHAVIOR THROUGH COMPOSITION class NumberProcessor def combine_and_triple(a, b, combiner)

    3 * combiner.process(a, b) end end class AdderDoubler def process(a, b) 2 * (a + b) end end NumberProcessor.new.combine_and_triple(4, 6, AdderDoubler.new)
  27. SPECIFY BEHAVIOR WITH A BLOCK class NumberProcessor def combine_and_triple(a, b)

    3 * yield(a, b) end end NumberProcessor.new.combine_and_triple(4, 6) { |x, y| 2 * (x + y) }
  28. FUNCTIONS ARE FIRST-CLASS OBJECTS ◦ Functions are data, just like

    the string “hi” and the number 3. ◦ You can pass functions as arguments to functions. ◦ You can return functions from functions.
  29. RETURNING FUNCTIONS FROM FUNCTIONS (defn double [x] (* 2 x))

    (defn one-and-a-half [x] (* (/ 3 2) x) x y
  30. RETURNING FUNCTIONS FROM FUNCTIONS (defn double [x] (* 2 x))

    (defn one-and-a-half [x] (* (/ 3 2) x) x y
  31. RETURNING FUNCTIONS FROM FUNCTIONS (defn double [x] (* 2 x))

    (defn average-damp [f] (fn [x] (average x (f x))))
  32. RETURNING FUNCTIONS FROM FUNCTIONS (defn double [x] (* 2 x))

    (defn average-damp [f] (fn [x] (average x (f x))))
  33. RETURNING FUNCTIONS FROM FUNCTIONS (defn double [x] (* 2 x))

    (defn average-damp [f] (fn [x] (average x (f x))))
  34. RETURNING FUNCTIONS FROM FUNCTIONS (defn double [x] (* 2 x))

    (defn average-damp [f] (fn [x] (average x (f x)))) (def damped-double (average-damp double))
  35. RETURNING FUNCTIONS FROM FUNCTIONS (defn double [x] (* 2 x))

    (defn average-damp [f] (fn [x] (average x (f x)))) (def damped-double (average-damp double)) ;= (fn [x] (average x (double x))
  36. RETURNING FUNCTIONS FROM FUNCTIONS (defn double [x] (* 2 x))

    (defn average-damp [f] (fn [x] (average x (f x)))) (def damped-double (average-damp double)) (damped-double 4)
  37. RETURNING FUNCTIONS FROM FUNCTIONS (defn double [x] (* 2 x))

    (defn average-damp [f] (fn [x] (average x (f x)))) (def damped-double (average-damp double)) (damped-double 4) ;= ((fn [x] (average x (double x))) 4)
  38. RETURNING FUNCTIONS FROM FUNCTIONS (defn double [x] (* 2 x))

    (defn average-damp [f] (fn [x] (average x (f x)))) (def damped-double (average-damp double)) (damped-double 4) ;= ((fn [x] (average x (double x))) 4) ;= (average 4 (double 4))
  39. RETURNING FUNCTIONS FROM FUNCTIONS (defn double [x] (* 2 x))

    (defn average-damp [f] (fn [x] (average x (f x)))) (def damped-double (average-damp double)) (damped-double 4) ;= ((fn [x] (average x (double x))) 4) ;= (average 4 (double 4)) ;= (average 4 8)
  40. RETURNING FUNCTIONS FROM FUNCTIONS (defn double [x] (* 2 x))

    (defn average-damp [f] (fn [x] (average x (f x)))) (def damped-double (average-damp double)) (damped-double 4) ;= ((fn [x] (average x (double x))) 4) ;= (average 4 (double 4)) ;= (average 4 8) ;= 6
  41. IN RUBY class Damper def double ->(x){ 2 * x

    } end def average_damp ->(x){ (x + yield.call(x)) / 2 } end def damped_double ->(x){ average_damp { double } } end end damper = Damper.new damper.average_damp { damper.double }.call(4) #=> 6
  42. BACK IN CLOJURE (defn average-damp [f] (fn [x] (average x

    (f x)))) (defn double [x] (* 2 x)) (def damped-double (average-damp double))
  43. RETURNING FUNCTIONS FROM FUNCTIONS (defn average-damp [f] (fn [x] (average

    x (f x)))) (defn triple [x] (* 3 x)) (def damped-triple (average-damp triple))
  44. RETURNING FUNCTIONS FROM FUNCTIONS (defn average-damp [f] (fn [x] (average

    x (f x)))) (def damped-cosine (average-damp Math/cos))
  45. RETURNING FUNCTIONS FROM FUNCTIONS (defn average-damp [f] (fn [x] (average

    x (f x)))) (def damped-cosine (average-damp Math/cos)) (def doubly-damped-cosine (average-damp (average-damp Math/cos)))
  46. RETURNING FUNCTIONS FROM FUNCTIONS (defn average-damp [f] (fn [x] (average

    x (f x)))) (def triply-damped-cosine (average-damp (average-damp (average-damp Math/cos))))
  47. “ The shape of a program should reflect only the

    problem it needs to solve. Any other regularity in the code is a sign...that I'm using abstractions that aren't powerful enough… - Paul Graham
  48. “ 16 of 23 patterns [in the Design Patterns book]

    are either invisible or simpler [in Lisp] - Peter Norvig
  49. “ Clojure demands that you raise your game, and pays

    you back for doing so. - Clojure Programming
  50. WHY USE CLOJURE? 1. It’s powerful. 2. It will make

    you a better programmer. 3. It’s fast.
  51. WHY USE CLOJURE? 1. It’s powerful. 2. It will make

    you a better programmer. 3. It’s fast. 4. It makes concurrency easy.
  52. CONCURRENCY IN RUBY # @inventory has 4000 hats. threads =

    [] 40.times do threads << Thread.new do 100.times { @inventory.decrease(:hats) } end end threads.each(&:join) puts @inventory[:hats]
  53. JRUBY’S CONCURRENCY BASICS 1. Don't do it. 2. If you

    must do it, don't share data across threads.
  54. JRUBY’S CONCURRENCY BASICS 1. Don't do it. 2. If you

    must do it, don't share data across threads. 3. If you must share data across threads, don't share mutable data.
  55. JRUBY’S CONCURRENCY BASICS 1. Don't do it. 2. If you

    must do it, don't share data across threads. 3. If you must share data across threads, don't share mutable data. 4. If you must share mutable data across threads, synchronize access to that data.
  56. WHY USE CLOJURE? 1. It’s powerful. 2. It will make

    you a better programmer. 3. It’s fast. 4. It makes concurrency easy. 5. It uses the JVM.
  57. WHY USE CLOJURE? 1. It’s powerful. 2. It will make

    you a better programmer. 3. It’s fast. 4. It makes concurrency easy. 5. It uses the JVM. 6. It favors immutability.
  58. “ ...functional programs not having state modification except in very

    limited constrained ways really does lead to programs that are easier to understand. - Brian Marick
  59. DON'T MUTATE IF YOU DON'T HAVE TO class MyAwesomeClass def

    with_city(geo_data) geo_data.merge!(city: 'Raleigh') end end
  60. DON'T MUTATE IF YOU DON'T HAVE TO class MyAwesomeClass def

    with_city(geo_data) geo_data.merge(city: 'Raleigh') end end
  61. DON'T MUTATE IF YOU DON'T HAVE TO module CityHelper def

    please_dont_do_this params[:city] = 'Raleigh' end end
  62. APPLICATIONS FOR RUBY 1. Don't mutate if you don't have

    to 2. Learn to love the block 3. Learn to love Enumerable#inject
  63. APPLICATIONS FOR RUBY 1. Don't mutate if you don't have

    to 2. Learn to love the block 3. Learn to love Enumerable#inject 4. Use the right tool for the job
  64. We talked about Clojure ◦ What? ◦ Why? ◦ How

    do I get started? ◦ How does this help my Ruby?
  65. CREDITS ◦ Presentation template by SlidesCarnival ◦ Some photographs by

    Unsplash ◦ Thread problem: http://www.jstorimer.com/pages/ruby-core-classes-arent- thread-safe ◦ Average damping problem: SICP ◦ https://github.com/jruby/jruby/wiki/Concurrency-in-jruby#concurrency_basics ◦ Jackie Chan image: troll.me ◦ Mind blown image: http://giphy.com/gifs/tim-and-eric-mind-blown- EldfH1VJdbrwY ◦ Paul Graham quote: http://www.paulgraham.com/icad.html ◦ Peter Norvig quote: in PDF found here: http://www.norvig.com/design-patterns/