Feature #14423
closedEnumerator from single object
Description
UPD: Current proposal
Introduce method Object#enumerate
for producing infinite enumerator by applying block to result of previous call.
Reference implementation:
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:
# 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 "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
- Ramda converge
- Elixir Stream#unfold (ends iteration when
nil
is returned) - Scala Iterator#iterate (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!):
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:
# 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 "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
- Ramda converge
- Elixir Stream#unfold (ends iteration when
nil
is returned) - Scala Iterator#iterate (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 toreduce
),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.