Project

General

Profile

Feature #14423

Updated by zverok (Victor Shepelev) over 6 years ago

**UPD: Current proposal** 

 Introduce method `Object#enumerate` for producing infinite enumerator by applying block to result of previous call. 

 Reference implementation: 

 ```ruby 
 class Object 
   def enumerate(&block) 
     Enumerator.new { |y| 
       val = self 
       y << val 
       loop do 
         val = block.call(val) 
         y << val 
       end 
     } 
   end 
 end 
 ``` 

 Possible usages: 
 ```ruby 
 # Most idiomatic "infinite sequence" possible: 
 p 1.enumerate(&:succ).take(5) 
 # => [1, 2, 3, 4, 5] 

 # Easy Fibonacci 
 p [0, 1].enumerate { |f0, f1| [f1, f0 + f1] }.take(10).map(&:first) 
 #=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] 

 # Enumerable pagination 
 page.enumerate { |page| Faraday.get(page.next) if page.next }.take_while { |p| !p.nil? } 
 ``` 

 Reference to similar things: 

 * Clojure [iterate](https://clojuredocs.org/clojure.core/iterate) "Returns a lazy sequence of `x`, `(f x)`, `(f (f x))` etc." No converging, just infinite sequence... And maybe that is even more basic and idiomatic. The name is nice, too. 
 * WolframLang [FixedPoint](http://reference.wolfram.com/language/ref/FixedPoint.html) 
 * Ramda [converge](http://ramdajs.com/docs/#converge) 
 * Elixir [Stream#unfold](https://hexdocs.pm/elixir/Stream.html#unfold/2) (ends iteration when `nil` is returned) 
 * Scala [Iterator#iterate](https://www.scala-lang.org/api/current/scala/collection/Iterator$.html#iterate%5BT%5D%28start%3AT%29%28f%3AT%3D%3ET%29%3AIterator%5BT%5D) (just infinite sequence) 

 --- 

 **Initial proposal** 

 Sometimes (or rather often), there is a programming pattern of "start from one object, do something, look at the result, do the same, look at the result (and so on)".  

 Examples: 

 * fetch page by URL, if pagination present, fetch next page; 
 * take 10 jobs from the queue, process them, exit when queue is empty; 

 Typically, those are represented by `while` or `loop` + `break` which somehow feels "not functional enough", and even "not Ruby enough", so much less expressive than `map` and other `Enumerable`/`Enumerator`-based cycles. 

 In some functional languages or libraries, there is function named `FixedPoint` or `converge`, whose meaning is "take an initial value, repeat the block provided on the result on prev computation, till it will not 'stable'". I believe this notion can be useful for Rubyists too. 

 Reference implementation (name is disputable!): 

 ```ruby 
 class Object 
   def converge(&block) 
     Enumerator.new { |y| 
       prev = self 
       y << self 
       loop do 
         cur = block.call(prev) 
         raise StopIteration if cur == prev 
         y << cur 
         prev = cur 
       end 
     } 
   end 
 end 
 ``` 

 Examples of usage: 

 ```ruby 
 # Functional kata: find the closest number to sqrt(2): 
 1.0.converge { |x| (x + 2 / x) / 2 }.to_a.last # => 1.414213562373095 
 Math.sqrt(2) # => 1.4142135623730951 

 # Next page situation: 
 get(url).converge { |page| page.next }  
 # => returns [page, page.next, page.next.next, ...til the result is nil, or same page repeated] 

 # Job queue situation: 
 queue.top(10).converge { |jobs|  
   jobs.each(&:perform) 
   queue.top(10)  
 } 
 # => takes top 10 jobs, till queue is empty (`[]` is returned two successful times) 

 # Useful for non-converging situations, too: 
 2.converge { |x| x ** 2 }.take(4) 
 # => [2, 4, 16, 256] 

 # Idiomatic Fibonacci: 
 [0, 1].converge { |f0, f1| [f1, f0 + f1] }.take(10).map(&:first) 
 # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] 
 ``` 

 Reference to similar things: 

 * Clojure [iterate](https://clojuredocs.org/clojure.core/iterate) "Returns a lazy sequence of `x`, `(f x)`, `(f (f x))` etc." No converging, just infinite sequence... And maybe that is even more basic and idiomatic. The name is nice, too. 
 * WolframLang [FixedPoint](http://reference.wolfram.com/language/ref/FixedPoint.html) 
 * Ramda [converge](http://ramdajs.com/docs/#converge) 
 * Elixir [Stream#unfold](https://hexdocs.pm/elixir/Stream.html#unfold/2) (ends iteration when `nil` is returned) 
 * Scala [Iterator#iterate](https://www.scala-lang.org/api/current/scala/collection/Iterator$.html#iterate%5BT%5D%28start%3AT%29%28f%3AT%3D%3ET%29%3AIterator%5BT%5D) (just infinite sequence) 

 Possible call-seq: 

 * If converges: `Object#converge(&block)`, `Enumerator.converge(object, &block)`; 
 * If just an infinite sequence: `Object#iterate(&block)`, `Object#deduce(&block)` (as opposed to `reduce`), `Enumerator.iterate(object, &block)`, `Enumerator#enumerate(object, &block)`. 

 WDYT?.. 

 PS: Can imagine somebody already proposed that, yet can't find nothing similar in the tracker for all keywords I've tried. 

Back