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

Thinking Functionally in Ruby

Tom Stuart
October 14, 2009

Thinking Functionally in Ruby

A talk about functional programming in general and Ruby's Enumerable module in particular.

Given at the October 2009 LRUG meeting (http://lrug.org/meetings/2009/09/18/october-2009-meeting/). There's a video of this talk at http://skillsmatter.com/podcast/ajax-ria/enumerators.

Tom Stuart

October 14, 2009
Tweet

More Decks by Tom Stuart

Other Decks in Programming

Transcript

  1. 2. ML-like •Standard ML •Haskell •OCaml •Scala (sort of) •Statically

    typed (+ reconstruction) •Code is not data •ADTs and pattern matching
  2. Referential transparency “an expression can be replaced with its value”

    • Good for efficiency • caching/memoisation/inlining • ultimately more efficient • Good for programmer understanding • state is incredibly hard to deal with • ultimately more intuitive • Good for code reuse and testing
  3. Future-proof •Mooreʼs Law is running out of steam •similar transistor

    density, more cores •The futureʼs concurrent •Concurrent access to mutable state is hard... •but not if you donʼt have mutable state! •Parallelising an algorithm is hard... •but itʼs easier if your code isnʼt overspecified
  4. Ruby can do some of this. Not really a functional

    language, but we can pretend and get some of the benefits.
  5. [1, 2, 3, 4]. zip([5, 6, 7, 8]) [1, 2,

    3, 4]. zip([5, 6, 7, 8])
  6. [ ] 2 1 3 4 6 5 7 8

    [ ] , , , , [ ] [ ] [ ] , , ,
  7. 1 2 3 4 2 1 3 4 ? ?

    ? ? ! " " !
  8. 1 2 3 4 2 1 3 4 ? ?

    ? ? ! " " !
  9. 1 ? ! 4 ? " 2 ? " 3

    ? ! 1 3 2 4
  10. 1 2 3 4 2 1 3 4 6 3

    9 12 ×3 ×3 ×3 ×3
  11. 1 2 3 4 2 1 3 4 6 3

    9 12 ×3 ×3 ×3 ×3
  12. [1, 2, 3, 4]. inject(0) { |x,y| x+y } [1,

    2, 3, 4]. inject(0) { |x,y| x+y }
  13. 1 2 3 4 1 2 3 4 0 1

    3 6 10 + + + +
  14. 1 1 2 3 3 6 4 0 1 2

    3 4 0 1 3 6 10 + + + +
  15. module Enumerable def inject(initial) result = initial for element in

    self result = yield(result, element) end result end end
  16. [1, 2, 3, 4]. inject { |x,y| x+y } [2,

    3, 4]. inject(1) { |x,y| x+y }
  17. >> ['El', 'rug']. inject(0) { |l,s| l + s.length }

    => 5 >> ['El', 'rug']. inject { |l,s| l + s.length } TypeError: can't convert Fixnum into String from (irb):1:in `+' from (irb):1 from (irb):1:in `inject' from (irb):1:in `each' from (irb):1:in `inject' from (irb):1
  18. >> [].inject { |x,y| x+y } => nil >> [].inject(0)

    { |x,y| x+y } => 0 >> [].inject(1) { |x,y| x*y } => 1
  19. [1, 2, 3, 4]. map { |x| x * 3

    }. inject(0) { |x| x+y }
  20. 1 2 3 4 2 1 3 4 6 3

    9 12 ×3 ×3 ×3 ×3 0 0 3 9 18 30 + + + +
  21. 3 6 9 12 1 2 3 4 2 1

    3 4 6 3 9 12 ×3 ×3 ×3 ×3 3 9 18 0 0 3 9 18 30 + + + +
  22. result = '' for name in names unless result.empty? result

    << ', ' end result << name end result
  23. result = '' for name in names unless result.empty? result

    << ', ' end result << name end result names.join(', ') !
  24. def count_mines_near(x, y) count = 0 for i in x-1..x+1

    for j in y-1..y+1 count += 1 if mine_at?(i, j) end end count end
  25. def count_mines_near(x, y) count = 0 for i in x-1..x+1

    for j in y-1..y+1 count += 1 if mine_at?(i, j) end end count end def count_mines_near(x, y) ((x-1..x+1).entries * 3).sort. zip((y-1..y+1).entries * 3). select { |x, y| mine_at?(x, y) }. length end !
  26. def count_mines_near(x, y) ((x-1..x+1).entries * 3).sort. zip((y-1..y+1).entries * 3). select

    { |x, y| mine_at?(x, y) }. length end [1, 2, 3, 1, 2, 3, 1, 2, 3] [1, 1, 1, 2, 2, 2, 3, 3, 3] .sort = # = (2, 8)
  27. def count_mines_near(x, y) ((x-1..x+1).entries * 3).sort. zip((y-1..y+1).entries * 3). select

    { |x, y| mine_at?(x, y) }. length end [1, 2, 3, 1, 2, 3, 1, 2, 3] [1, 1, 1, 2, 2, 2, 3, 3, 3] [7, 8, 9, 7, 8, 9, 7, 8, 9] [[1, 7], [1, 8], [1, 9], [2, 7], [2, 8], [2, 9], [3, 7], [3, 8], [3, 9]] .sort = .zip( ) = # = (2, 8)
  28. def count_mines_near(x, y) ((x-1..x+1).entries * 3).sort. zip((y-1..y+1).entries * 3). select

    { |x, y| mine_at?(x, y) }. length end [1, 2, 3, 1, 2, 3, 1, 2, 3] [1, 1, 1, 2, 2, 2, 3, 3, 3] [7, 8, 9, 7, 8, 9, 7, 8, 9] [[1, 7], [1, 8], [1, 9], [2, 7], [2, 8], [2, 9], [3, 7], [3, 8], [3, 9]] .sort = .zip( ) = .select {…} = [[1, 8], [3, 7]] .length = 2 # = (2, 8)
  29. def count_mines_near(x, y) ((x-500..x+500).entries * 1001).sort. zip((y-500..y+500).entries * 1001). select

    { |x, y| mine_at?(x, y) }. length end [ [1, 7], [1, 8], …, [1, 1007], [2, 7], [2, 8], …, [2, 1007], …, …, …, [1000, 7], [1000, 8], …, [1000, 1007], [1001, 7], [1000, 8], …, [1001, 1007]]
  30. [ [1, 7], [1, 8], …, [1, 1007], [2, 7],

    [2, 8], …, [2, 1007], …, …, …, [1000, 7], [1000, 8], …, [1000, 1007], [1001, 7], [1000, 8], …, [1001, 1007]] 173 96 121 78 237 705
  31. def numbers_near(n, radius) ((n - radius)..(n + radius)).entries end def

    squares_near(x, y, radius) diameter = (radius * 2) + 1 (numbers_near(x, radius) * diameter). sort. zip(numbers_near(y, radius) * diameter) end def count_mines_near(x, y, radius = 1) squares_near(x, y, radius). select { |x, y| mine_at?(x, y) }. length end