From: knu@... Date: 2018-10-18T14:24:51+00:00 Subject: [ruby-core:89458] [Ruby trunk Feature#14781] Enumerator#generate Issue #14781 has been updated by knu (Akinori MUSHA). File enumerator_from.rb added I've been thinking about this, and I have some ideas I want to share: - To recursively traverse ancestors of a node is one of the most typical use cases, so that should be made easy to do. - When and how to end a sequence may vary, so there should be some flexibility in defining an end. For example, nil is not always the dead end. It could mean something; you might even want to end a sequence with an explicit nil as sentinel. Rescuing an exception and treating it as an end might not be a good option because that would make debugging hard, but StopIteration should be a good fit as a signal for an end. - Sometimes you'd need to look back two or more steps to generate a new value (not to mention Fibonacci series), so the constructor should preferably take multiple seeds. - Sometimes seeds are not subject of yielding; it would be handy if you could specify how many leading seeds to skip. In the original proposal, there are some tricks needed to define an end of a sequence or to look back multiple preceding terms, so I've come up with an alternative API that builds them in as keyword options: ``` Enumerator.from(seeds, drop: 0, allow_nil: false) { |*preceding_terms| next_term } seeds: Array of objects to be used as seeds (required, but can be an empty array) drop: How many leading terms to skip allow_nil: True if nil should not end the enumerator ``` I wrote an experimental implementation and test cases in the attached file. I'll be working on it further in this weekends' hackathon, so any input is appreciated! ---------------------------------------- Feature #14781: Enumerator#generate https://bugs.ruby-lang.org/issues/14781#change-74498 * Author: zverok (Victor Shepelev) * Status: Feedback * Priority: Normal * Assignee: * Target version: ---------------------------------------- This is alternative proposal to `Object#enumerate` (#14423), which was considered by many as a good idea, but with unsure naming and too radical (`Object` extension). This one is _less_ radical, and, at the same time, more powerful. **Synopsys**: * `Enumerator.generate(initial, &block)`: produces infinite sequence where each next element is calculated by applying block to previous; `initial` is first sequence element; * `Enumerator.generate(&block)`: the same; first element of sequence is a result of calling the block with no args. This method allows to produce enumerators replacing a lot of common `while` and `loop` cycles in the same way `#each` replaces `for`. **Examples:** With initial value ```ruby # Infinite sequence p Enumerator.generate(1, &:succ).take(5) # => [1, 2, 3, 4, 5] # Easy Fibonacci p Enumerator.generate([0, 1]) { |f0, f1| [f1, f0 + f1] }.take(10).map(&:first) #=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] require 'date' # Find next Tuesday p Enumerator.generate(Date.today, &:succ).detect { |d| d.wday == 2 } # => # # Tree navigation # --------------- require 'nokogiri' require 'open-uri' # Find some element on page, then make list of all parents p Nokogiri::HTML(open('https://www.ruby-lang.org/en/')) .at('a:contains("Ruby 2.2.10 Released")') .yield_self { |a| Enumerator.generate(a, &:parent) } .take_while { |node| node.respond_to?(:parent) } .map(&:name) # => ["a", "h3", "div", "div", "div", "div", "div", "div", "body", "html"] # Pagination # ---------- require 'octokit' Octokit.stargazers('rails/rails') # ^ this method returned just an array, but have set `.last_response` to full response, with data # and pagination. So now we can do this: p Enumerator.generate(Octokit.last_response) { |response| response.rels[:next].get # pagination: `get` fetches next Response } .first(3) # take just 3 pages of stargazers .flat_map(&:data) # `data` is parsed response content (stargazers themselves) .map { |h| h[:login] } # => ["wycats", "brynary", "macournoyer", "topfunky", "tomtt", "jamesgolick", ... ``` Without initial value ```ruby # Random search target = 7 p Enumerator.generate { rand(10) }.take_while { |i| i != target }.to_a # => [0, 6, 3, 5,....] # External while condition require 'strscan' scanner = StringScanner.new('7+38/6') p Enumerator.generate { scanner.scan(%r{\d+|[-+*/]}) }.slice_after { scanner.eos? }.first # => ["7", "+", "38", "/", "6"] # Potential message loop system: Enumerator.generate { Message.receive }.take_while { |msg| msg != :exit } ``` **Reference implementation**: https://github.com/zverok/enumerator_generate I want to **thank** all peers that participated in the discussion here, on Twitter and Reddit. ---Files-------------------------------- enumerator_from.rb (3.16 KB) -- https://bugs.ruby-lang.org/ Unsubscribe: