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
nilis 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
nilis 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.