Functional Programming and Ruby

Functional Programming and Ruby

While Ruby is object oriented and imperative, it does have some features that allow for functional programming. This talk will compare how you would write functions in Haskell with Ruby, and then zoom in to take a close look at Ruby 2.0’s new “Lazy Enumerator” feature.

Eb5382b137146b381dd13740d165d1f2?s=128

Pat Shaughnessy

March 12, 2013
Tweet

Transcript

  1. Functional Programming and Ruby Pat Shaughnessy http://patshaughnessy.net @pat_shaughnessy McKinsey &

    Co. Boston.rb March 12, 2013
  2. the founding father of Ruby

  3. None
  4. Ruby is a language designed in the following steps: *

    take a simple lisp language (like one prior to CL). * remove macros, s-expression. * add simple object system (much simpler than CLOS). * add blocks, inspired by higher order functions. * add methods found in Smalltalk. * add functionality found in Perl (in OO way). So, Ruby was a Lisp originally, in theory. Let's call it MatzLisp from now on. ;-) ! ! ! ! ! ! ! matz. http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/179642 Feb. 2006
  5. the founding grandfathers of Ruby

  6. None
  7. None
  8. None
  9. None
  10. why learn a foreign language?

  11. None
  12. quicksort :: Ord a => [a] -> [a] quicksort []

    = [] quicksort (p:xs) = (quicksort lesser) ++ [p] ++ (quicksort greater) where lesser = filter (< p) xs greater = filter (>= p) xs
  13. Haskell... is a polymorphically statically typed, lazy, purely functional language,

    quite different from most other programming languages. The language is named for Haskell Brooks Curry, whose work in mathematical logic serves as a foundation for functional languages. Haskell is based on the lambda calculus, hence the lambda we use as a logo. http://www.haskell.org/haskellwiki/Introduction
  14. - what is “functional programming?” - higher order functions -

    lazy evaluation - memoization - resources for learning more
  15. what is “functional programming?”

  16. None
  17. http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey

  18. higher order functions

  19. [1..10] =>[1, 2, 3, 4, 5, 6, 7, 8, 9,

    10] (1..10).to_a
  20. [ x*x | x <- [1..10]] (1..10).collect { |x| x*x

    } =>[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] (1..10).map { |x| x*x }
  21. None
  22. map (\x -> x*x) [1..10] (1..10).map &lambda { |x| x*x

    } =>[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] (1..10).map &(->(x) { x*x })
  23. None
  24. [ x+1 | x <- [ x*x | x <-

    [1..10]]] =>[2, 5, 10, 17, 26, 37, 50, 65, 82, 101]
  25. [1..10] f(x) = x*x g(x) = x+1 [2..101] f(1) =

    1 g(1) = 2 Step 1: f(2) = 4 g(4) = 5 Step 2: f(3) = 9 g(9) = 10 Step 3: f(4) = 16 g(16) = 17 Step 4:
  26. (1..10).collect { |x| x*x } .collect { |x| x+1 }

    =>[2, 5, 10, 17, 26, 37, 50, 65, 82, 101]
  27. what’s happening inside of Ruby?

  28. (1..10).collect { |x| x*x } each Range Enumerable #collect Enumerable#collect

  29. x*x [1..100] yield loop: 1 -> 10 collect_i (1..10).collect {

    |x| x*x } Enumerable#collect
  30. Enumerable#collect def collect(range, &block) result = [] range.each do |x|

    result << block.call(x) end result end
  31. each Range Enumerable #collect each Enumerable #collect Enumerable#collect chain

  32. [1..10] f(x) = x*x [1..100] g(x) = x+1 [2..101] Step

    1: Call f(x) 10 times Step 2: Call g(x) 10 times
  33. lazy evaluation

  34. [1..] =>[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28, 29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54, 55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80, 81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,1 05,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123, etc...

  35. take 10 [1..] =>[1,2,3,4,5,6,7,8,9,10]

  36. take 10 [ x+1 | x <- [ x*x |

    x <- [1..]]] =>[2,5,10,17,26,37,50,65,82,101]
  37. (1..Float::INFINITY).lazy.collect { |x| x*x } .collect { |x| x+1 }

    .take(10).force =>[2,5,10,17,26,37,50,65,82,101]
  38. (1..Float::INFINITY).lazy.collect { |x| x*x } .collect { |x| x+1 }

    .first(10) =>[2,5,10,17,26,37,50,65,82,101]
  39. what’s happening inside of Ruby?

  40. fib = Enumerator.new do |yielder| a = b = 1

    loop do yielder << a a, b = b, a + b end end p fib.take(10) # => [1, 1, 2, 3, 5, 8... Enumerator object http://ruby-doc.org/core-2.0/Enumerator.html
  41. Enumerator object Enumerator [1..100] block yielder

  42. Enumerable#collect def collect(range, &block) result = [] range.each do |x|

    result << block.call(x) end result end
  43. def lazy_collect(range, &block) Enumerator.new do |yielder| range.each do |value| yielder.yield(block.call(value))

    end end end Lazy::Enumerator object: hard coded block inside Ruby http://code.google.com/p/tokland/wiki/RubyFunctionalProgramming
  44. (1..10).lazy.collect { |x| x*x } each Range Enumerable #collect Enumerator::Lazy#collect

  45. Enumerator::Lazy#collect Enumerator creates loop: 1 -> 10 lazy_map (1..10).lazy.collect {

    |x| x*x }.first(10)
  46. Enumerator::Lazy#collect Enumerator loop: 1 -> 10 (1..10).lazy.collect { |x| x*x

    }.first(10) x*x yields once [1..100] returns
  47. (1..Float::INFINITY).lazy.collect { |x| x*x } .collect { |x| x+1 }.first(10)

    yield to the blocks, one at a time Enumerator Enumerator Stop After 10 yield pass result pass result x*x x+1 [2..101] store yield yield loop
  48. memoization

  49. slow_fib 0 = 0 slow_fib 1 = 1 slow_fib n

    = slow_fib (n-2) + slow_fib (n-1) map slow_fib [1..10] => [1,1,2,3,5,8,13,21,34,55] http://www.haskell.org/haskellwiki/Memoization
  50. None
  51. memoized_fib = (map fib [0 ..] !!) where fib 0

    = 0 fib 1 = 1 fib n = memoized_fib (n-2) + memoized_fib (n-1) Typical Haskell magic! http://www.haskell.org/haskellwiki/Memoization
  52. (map fib [0 ..] !!) Infinite, lazy list of return

    values A curried function to return the requested fib
  53. [0 ..] (0..Float::INFINITY)

  54. map fib [0 ..] (0..Float::INFINITY).lazy.map {|x| fib(x) }

  55. (map fib [0 ..] !!) cache = (0..Float::INFINITY).lazy.map {|x| fib(x)

    } nth_element_from_list = lambda { |ary, n| ary[n]} nth_fib = nth_element_from_list.curry[cache]
  56. `block in <main>': undefined method `[]' for #<Enumerator::Lazy: #<Enumerator::Lazy: 0..Infinity>:map>

    (NoMethodError) map memoized_fib [1..10] => [1,1,2,3,5,8,13,21,34,55]
  57. what’s happening inside of Ruby?

  58. (0..Float::INFINITY).lazy.map {|x| fib(x) } nth_element_from_list = lambda { |ary, n|

    ary[n]} each Range Enumerable #collect
  59. @cache = {}; @cache[1] = 1; @cache[2] = 1 def

    memoized_fib(n) @cache[n] ||= memoized_fib(n-1) + memoized_fib(n-2) end
  60. What have we learned? - Ruby has a lot of

    functional features! Ruby 2.0 has even more. - Ruby and Haskell can resemble each other... on the surface. - However, under the hood Ruby’s support of functional programming is limited.
  61. resources for learning more

  62. http://landoflisp.com

  63. http://learnyouahaskell.com

  64. https://leanpub.com/fp-oo

  65. http://www.kickstarter.com/projects/1225193080/the-ruby-20-walkthrough

  66. http://rubysource.com/functional-programming-techniques- with-ruby-part-i/ http://code.google.com/p/tokland/wiki/ RubyFunctionalProgramming

  67. Thanks! Pat Shaughnessy http://patshaughnessy.net @pat_shaughnessy