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

Enumerator - Enumerable's Ugly Cousin

Enumerator - Enumerable's Ugly Cousin

Why don't more Rubyists use Enumerator? Learn more about this beautiful Ruby concept that could use a little more love.

Ross Kaffenberger

March 31, 2016
Tweet

More Decks by Ross Kaffenberger

Other Decks in Technology

Transcript

  1. NYC

  2. for loops are useful! C++ MATLAB Perl Algol BASIC PostScript

    C Pascal Ada Bash Haskell Python Lua Java JavaScript PHP ActionScript Go
  3. – Zed Shaw “When you teach people social norms as

    if they are universal truths you are actually indoctrinating them not educating them. .”
  4. enum = [1, 2, 3].to_enum # => #<Enumerator: [1, 2,

    3]:each> enum.next # => 1 enum.next # => 2 enum.next # => 3 enum.next # => StopIteration enum.each { |x| # ... }
  5. ArrayList list = list.add( list.add( list.add( ArrayList list = new

    ArrayList(); list.add(1); list.add(2); list.add(3); Iterator it = list.iterator(); while(it.hasNext()) { Object element = it.next(); System.out.println(element); };
  6. ArrayList list = list.add( list.add( list.add( Iterator it = list.iterator();

    while Object element = it.next(); System.out.println(element); }; it.next();
  7. def each yield 1 # to block yield 2 #

    to block yield 3 # to block end enum = [1, 2, 3].to_enum enum.next # yield 1 enum.next # yield 2 enum.next # yield 3
  8. <table> <% projects.each do |project| %> <tr style="background: <%= colors.next

    %>"> <td><%= project.name %></td> </tr> <% end %> </table> <% colors = %w[aliceblue ghostwhite].to_enum(:cycle) %>
  9. gen = python_generator() gen.next() # => 1 gen.next() # =>

    2 gen.next() # => 3 def python_generator(): yield 1 yield 2 yield 3
  10. def fibonacci(n): result = [] a = b = 1

    for i in xrange(n): result.append(a) a, b = b, a + b return result
  11. def fibonacci(n): result = [] a = b = 1

    for i in xrange(n): result.append(a) a, b = b, a + b return result for a in fibonacci(20): print a
  12. def fibonacci(n): a = b = 1 for i in

    xrange(n): a, b = b, a + b for a in fibonacci(20): print a
  13. def fibonacci(n): a = b = 1 for i in

    xrange(n): yield a a, b = b, a + b for a in fibonacci(20): print a
  14. def fibonacci(n): a = b = 1 for i in

    xrange(n): yield a a, b = b, a + b for a in fibonacci(20): print a
  15. def fibonacci(n) a = b = 1 n.times do yield

    a a, b = b, a + b end end fibonacci(20) { |a| puts a } Not enumerable!
  16. def fibonacci(n) a = b = 1 n.times do yield

    a a, b = b, a + b end end def a = b = n.times a, b = b, a + b end return to_enum(__method__, n) unless block_given?
  17. def fibonacci(n) a = b = 1 n.times do yield

    a a, b = b, a + b end end def a = b = n.times a, b = b, a + b end fibonacci(7).each { |a| puts a } fibonacci(25).map { |a| a * 3 }.select(&:odd?) fibonacci(100).find { |a| a > 50 } return to_enum(__method__, n) unless block_given? ] [ Enumerable!
  18. [1, 2, 3].to_enum(:each) # => #<Enumerator: [1, 2, 3]:each> [1,

    2, 3].to_enum(:map) # => #<Enumerator: [1, 2, 3]:map> [1, 2, 3].to_enum(:select) # => #<Enumerator: [1, 2, 3]:select> [1, 2, 3].to_enum(:group_by) # => #<Enumerator: [1, 2, 3]:group_by> [1, 2, 3].to_enum(:cycle) # => #<Enumerator: [1, 2, 3]:cycle>
  19. [1, 2, 3].each # => #<Enumerator: [1, 2, 3]:each> [1,

    2, 3].map # => #<Enumerator: [1, 2, 3]:map> [1, 2, 3].select # => #<Enumerator: [1, 2, 3]:select> [1, 2, 3].group_by # => #<Enumerator: [1, 2, 3]:group_by> [1, 2, 3].cycle # => #<Enumerator: [1, 2, 3]:cycle>
  20. Fixnum 4.times 1.upto(10) 10.upto(1) Range (1..10).each (1..10).step Struct struct.each struct.each_pair

    String "123".each_byte "123".each_char "123".each_codepoint "123".each_line "123".gsub File File.foreach file.each file.each_line file.each_byte file.each_codepoint ObjectSpace ObjectSpace.each_object loop
  21. obj_to_enum(int argc, VALUE *argv, VALUE obj) { VALUE enumerator, meth

    = sym_each; if (argc > 0) { --argc; meth = *argv++; } enumerator = rb_enumeratorize_with_size(obj, meth, argc, argv, 0); if (rb_block_given_p()) { enumerator_ptr(enumerator)->size = rb_block_proc(); } return enumerator; } static VALUE enumerator.c
  22. obj_to_enum( { VALUE enumerator, meth = sym_each; --argc; meth =

    *argv++; } enumerator = rb_enumeratorize_with_size(obj, meth, argc, argv, enumerator_ptr(enumerator)->size = rb_block_proc(); } return enumerator; } rb_enumeratorize_with_size(obj, meth, static VALUE enumerator.c
  23. VALUE rb_enumeratorize_with_size(VALUE obj, VALUE meth, int *size_fn) { dispatching to

    either return lazy_to_enum_i(obj, meth, argc, argv, size_fn); return enumerator_init( enumerator_allocate(rb_cEnumerator), } return enumerator_init( enumerator_allocate(rb_cEnumerator), obj, meth, argc, argv, size_fn, Qnil); rb_enumeratorize_with_size
  24. static next_i { VALUE nil VALUE result; result next_ii e

    "iteration reached an end" return } next_i(VALUE curr, VALUE obj) return rb_fiber_yield(1, &nil); enumerator.c Fiber!
  25. Fiber.new fiber = Fiber.new do Fiber.yield 1 Fiber.yield 2 Fiber.yield

    3 end fiber.resume # => 1 fiber.resume # => 2 fiber.resume # => 3
  26. gen = python_generator() gen.next() # => 1 gen.next() # =>

    2 gen.next() # => 3 def python_generator(): yield 1 yield 2 yield 3
  27. Fiber.new fiber = Fiber.new do Fiber.yield 1 Fiber.yield 2 Fiber.yield

    3 end fiber.resume # => 1 fiber.resume # => 2 fiber.resume # => 3
  28. class CustomEnumerator def initialize(collection, meth, *args) @collection = collection @fiber

    = Fiber.new do @collection.send(meth, *args) do |n| Fiber.yield(n) end raise StopIteration end end end class CustomEnumerator def initialize(collection, meth, *args) @collection = collection class CustomEnumerator
  29. class CustomEnumerator include Enumerable def next @fiber.resume end def each

    loop { yield self.next } end end # => StopIteration
  30. class PaginatedApiClient def posts(page=0) return to_enum(:posts, page) unless bg? #

    fetch page # yield each post # call posts(page+1) end end
  31. class Document include Enumerable def each # yield each end

    end paragraph word line class Document
  32. class Document def each_line return to_enum(:each_line) unless block_given? # yield

    each line end def each_word return to_enum(:each_word) unless block_given? # yield each word end def each_para return to_enum(:each_para) unless block_given? # yield each paragraph end end class Document def each_line return to_enum(:each_line) unless block_given? # yield each line end class Document def each_line return to_enum(:each_line) unless block_given? # yield each line end def each_word return to_enum(:each_word) unless block_given? # yield each word end class Document
  33. class BinaryTree def breadth_first return to_enum(__method__) unless block_given? # yield

    values in “breadth first” order end def pre_order # etc… end def post_order end def in_order end end
  34. tree = tree.breadth_first. with_index. partition { |n, i| i.odd? }.

    flat_map(&:join) # => “b1d3f5”, “a0c2e4”
  35. (take 2 (filter odd? [0 1 2 3 4 5]))

    [0, 1, 2, 3, 4, 5] |> Enum.filter(&Integer.is_odd/1) |> Enum.take(2)
  36. (take 2 (filter odd? (iterate inc 0))) Stream.iterate(0, &(&1 +

    1)) |> Stream.filter(&Integer.is_odd/1) |> Enum.take(2)
  37. def fibonacci Enumerator.new do |y| a = b = 1

    loop do y.yield a a, b = b, a + b end end end like Fiber.yield!
  38. def a = b = y.yield a a, b =

    b, a + b end fibonacci.lazy. select(&:odd?). take(3)
  39. # Expressive Montrose.weekly(on: :monday, at: "9 am") # => #<Montrose::Recurrence...>

    # Flexible Montrose.hourly.interval(3) Montrose.every(3.hours) Montrose.r(every: 3.hours) # Chainable Montrose.monthly. starting(1.year.from_now). on(friday: 13). repeat(5)
  40. # Enumerable r = Montrose.monthly r.until(1.year.from_now).each do |event| # …

    end r.lazy.chunk(&:month).take(8).to_a # => [ [4, 2016-04-06 12:00:00 -0500], [5, 2016-05—06 12:00:00 -0500], [6, 2016-06—06 12:00:00 -0500], ...]
  41. –Me “I wasn’t capable of writing Montrose a few years

    go. I needed to get out of my comfort zone.”