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. 3.
  2. 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
  3. 6.
  4. 7.
  5. 8.
  6. 9.
  7. 11.
  8. 12.

    quicksort :: Ord a => [a] -> [a] quicksort []

    = [] quicksort (p:xs) = (quicksort lesser) ++ [p] ++ (quicksort greater) where lesser = filter (< p) xs greater = filter (>= p) xs
  9. 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
  10. 14.

    - what is “functional programming?” - higher order functions -

    lazy evaluation - memoization - resources for learning more
  11. 16.
  12. 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 }
  13. 21.
  14. 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 })
  15. 23.
  16. 24.

    [ x+1 | x <- [ x*x | x <-

    [1..10]]] =>[2, 5, 10, 17, 26, 37, 50, 65, 82, 101]
  17. 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:
  18. 26.

    (1..10).collect { |x| x*x } .collect { |x| x+1 }

    =>[2, 5, 10, 17, 26, 37, 50, 65, 82, 101]
  19. 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
  20. 36.

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

    x <- [1..]]] =>[2,5,10,17,26,37,50,65,82,101]
  21. 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]
  22. 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]
  23. 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
  24. 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
  25. 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
  26. 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
  27. 50.
  28. 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
  29. 52.

    (map fib [0 ..] !!) Infinite, lazy list of return

    values A curried function to return the requested fib
  30. 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]
  31. 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]
  32. 59.

    @cache = {}; @cache[1] = 1; @cache[2] = 1 def

    memoized_fib(n) @cache[n] ||= memoized_fib(n-1) + memoized_fib(n-2) end
  33. 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.