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

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.

Pat Shaughnessy

March 12, 2013
Tweet

More Decks by Pat Shaughnessy

Other Decks in Technology

Transcript

  1. Functional
    Programming
    and Ruby
    Pat Shaughnessy
    http://patshaughnessy.net
    @pat_shaughnessy
    McKinsey & Co.
    Boston.rb
    March 12, 2013

    View Slide

  2. the founding father of Ruby

    View Slide

  3. View Slide

  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

    View Slide

  5. the founding grandfathers of Ruby

    View Slide

  6. View Slide

  7. View Slide

  8. View Slide

  9. View Slide

  10. why learn a foreign language?

    View Slide

  11. View Slide

  12. quicksort :: Ord a => [a] -> [a]
    quicksort [] = []
    quicksort (p:xs) =
    (quicksort lesser)
    ++ [p]
    ++ (quicksort greater)
    where
    lesser = filter (< p) xs
    greater = filter (>= p) xs

    View Slide

  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

    View Slide

  14. - what is “functional programming?”
    - higher order functions
    - lazy evaluation
    - memoization
    - resources for learning more

    View Slide

  15. what is “functional programming?”

    View Slide

  16. View Slide

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

    View Slide

  18. higher order functions

    View Slide

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

    View Slide

  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 }

    View Slide

  21. View Slide

  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 })

    View Slide

  23. View Slide

  24. [ x+1 | x <- [ x*x | x <- [1..10]]]
    =>[2, 5, 10, 17, 26, 37, 50, 65, 82, 101]

    View Slide

  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:

    View Slide

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

    View Slide

  27. what’s happening inside of Ruby?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  31. each
    Range
    Enumerable
    #collect
    each
    Enumerable
    #collect
    Enumerable#collect chain

    View Slide

  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

    View Slide

  33. lazy evaluation

    View Slide

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

    View Slide

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

    View Slide

  36. take 10 [ x+1 | x <- [ x*x | x <- [1..]]]
    =>[2,5,10,17,26,37,50,65,82,101]

    View Slide

  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]

    View Slide

  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]

    View Slide

  39. what’s happening inside of Ruby?

    View Slide

  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

    View Slide

  41. Enumerator object
    Enumerator
    [1..100]
    block yielder

    View Slide

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

    View Slide

  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

    View Slide

  44. (1..10).lazy.collect { |x| x*x }
    each
    Range
    Enumerable
    #collect
    Enumerator::Lazy#collect

    View Slide

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

    View Slide

  46. Enumerator::Lazy#collect
    Enumerator
    loop:
    1 -> 10
    (1..10).lazy.collect { |x| x*x }.first(10)
    x*x
    yields
    once
    [1..100]
    returns

    View Slide

  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

    View Slide

  48. memoization

    View Slide

  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

    View Slide

  50. View Slide

  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

    View Slide

  52. (map fib [0 ..] !!)
    Infinite, lazy list of
    return values
    A curried function to
    return the requested fib

    View Slide

  53. [0 ..]
    (0..Float::INFINITY)

    View Slide

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

    View Slide

  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]

    View Slide

  56. `block in ': undefined method `[]'
    for #0..Infinity>:map> (NoMethodError)
    map memoized_fib [1..10]
    => [1,1,2,3,5,8,13,21,34,55]

    View Slide

  57. what’s happening inside of Ruby?

    View Slide

  58. (0..Float::INFINITY).lazy.map {|x| fib(x) }
    nth_element_from_list =
    lambda { |ary, n| ary[n]}
    each
    Range
    Enumerable
    #collect

    View Slide

  59. @cache = {}; @cache[1] = 1; @cache[2] = 1
    def memoized_fib(n)
    @cache[n] ||= memoized_fib(n-1) + memoized_fib(n-2)
    end

    View Slide

  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.

    View Slide

  61. resources for learning more

    View Slide

  62. http://landoflisp.com

    View Slide

  63. http://learnyouahaskell.com

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide