Issue #14781 has been updated by jwmittag (J=F6rg W Mittag).


zverok (Victor Shepelev) wrote:
> This is alternative proposal to `Object#enumerate` (#14423), which was co=
nsidered by many as a good idea, but with unsure naming and too radical (`O=
bject` extension). This one is _less_ radical, and, at the same time, more =
powerful.
> =

> **Synopsys**: =

> * `Enumerator.generate(initial, &block)`: produces infinite sequence wher=
e 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 is typically called `unfold`, since it is the category-theoretical dua=
l of a `fold`. Ruby doesn't use the name `fold`, though, it uses `inject` a=
nd `reduce`, neither of which lend themselves to negating: `Enumerator::uni=
nject`, `Enumerator::unreduce`? Yikes! That sounds really bad.

However, literally as I am writing this, a thought pops into my mind: how a=
bout `Enumerator::produce` as the dual to `Enumerable#reduce`? Scala also h=
as `iterate` which is the restricted variant of `unfold`.

----------------------------------------
Feature #14781: Enumerator.generate
https://bugs.ruby-lang.org/issues/14781#change-80616

* Author: zverok (Victor Shepelev)
* Status: Feedback
* Priority: Normal
* Assignee: =

* Target version: =

----------------------------------------
This is alternative proposal to `Object#enumerate` (#14423), which was cons=
idered by many as a good idea, but with unsure naming and too radical (`Obj=
ect` extension). This one is _less_ radical, and, at the same time, more po=
werful.

**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 r=
esult 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)
# =3D> [1, 2, 3, 4, 5]

# Easy Fibonacci
p Enumerator.generate([0, 1]) { |f0, f1| [f1, f0 + f1] }.take(10).map(&:fir=
st)
#=3D> [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 =3D=3D 2 }
# =3D> #<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")')
  .yield_self { |a| Enumerator.generate(a, &:parent) }
  .take_while { |node| node.respond_to?(:parent)  }
  .map(&:name)
# =3D> ["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 fu=
ll response, with data
# and pagination. So now we can do this:
p Enumerator.generate(Octokit.last_response) { |response| =

    response.rels[:next].get                         # pagination: `get` fe=
tches next Response
  } =

  .first(3)                                          # take just 3 pages of=
 stargazers
  .flat_map(&:data)                                  # `data` is parsed res=
ponse content (stargazers themselves)
  .map { |h| h[:login] }
# =3D> ["wycats", "brynary", "macournoyer", "topfunky", "tomtt", "jamesgoli=
ck", ...
```

Without initial value

```ruby
# Random search
target =3D 7
p Enumerator.generate { rand(10) }.take_while { |i| i !=3D target }.to_a
# =3D> [0, 6, 3, 5,....]

# External while condition
require 'strscan'
scanner =3D StringScanner.new('7+38/6')
p Enumerator.generate { scanner.scan(%r{\d+|[-+*/]}) }.slice_after { scanne=
r.eos? }.first
# =3D> ["7", "+", "38", "/", "6"]

# Potential message loop system:
Enumerator.generate { Message.receive }.take_while { |msg| msg !=3D :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)
enumerator-generate.patch (4.44 KB)


-- =

https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=3Dunsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>