Object#enumerate
This is a small (exactly 1 method) gem to showcase my proposal to core Ruby.
Discussion log from developers meeting:
Naruse: interesting proposal
Akr: adding method to Object sounds too radical to me.
Usa: I think there is a chance if this is a method of Enumerable.
Shyouhei: my feeling is this should start as a gem.
So, considering Shyouhei's last remark, I am providing this gem for interested parties to experiment.
I still strongly believe the method should be a part of language core, so the gem is made as a proof-of-concept, to make experimentation with an idea simple.
UPD: I thought about alternative proposal, Enumerator#generate
, which seems less radical and more powerful at the same time.
Synopsys
Object#enumerate
takes a block and returns an instance of infinite Enumerator
, where each next element is made by applying the block to the previous.
Examples of usage
Object#enumerate
can provide idiomatic replacement for a lot of while
and loop
constructs, the same way each
replaces for
.
require 'object_enumerate'
# 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]
# Find next Tuesday
require 'date'
Date.today.enumerate(&:succ).detect { |d| d.wday == 2 }
# => #<Date: 2018-05-22 ((2458261j,0s,0n),+0s,2299161j)>
# 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")')
.enumerate(&: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 Octokit.last_response
.enumerate { |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", ...
Alternative synopsys
Not implemented in the gem, just provided for a sake of core language proposal.
Enumerable.enumerate(1, &:succ)
Enumerable(1, &:succ)
Enumerator.from(1, &:succ)
I personally don't believe any of those look clear enough, so, however risky adding new method to Object
could look, I'd vote for it.
UPD: Naming
I received several responses about the name not being "obvious enough". It was not expected, I am sorry for not providing the reasons about name earlier.
The reasons behind the name #enumerate
:
- Core method names should be short and mnemonic, not long and descriptive. (Otherwise, we'd have
yield_each
instead ofeach
,yield_each_and_collect
instead ofmap
,parallel_each
instead ofzip
and don't even get me started onreduce
). It is if you can't guess what core method exactly does just from the name, more important to have it easily remembered and associative. - Code constructs using the name should be spellable in your head. I pronounce it "1: enumerate succeeding numbers", "last result: enumerate all next" and so on. Judge yourself.
- Concept is present in other languages and frequently named
iterate
(for example, Clojure and Scala). As we call our iteratorsEnumerator
, it is logical to call the methodenumerate
. - Once you got it, the name is hard to confuse with anything (the only other slightly similar name in core is
#to_enum
, but it is typically used in a very far context).
The only other reasonable option I can think about is deduce
, as an antonym for reduce
which makes the opposite thing. Elixir follows this way, calling the methods fold
(for our reduce
) and unfold
(for method proposed).