https://bugs.ruby-lang.org/https://bugs.ruby-lang.org/favicon.ico2012-11-05T23:01:51ZRuby Issue Tracking SystemRuby trunk - Bug #7248: Shouldn't Enumerator::Lazy.new be private?https://bugs.ruby-lang.org/issues/7248?journal_id=324272012-11-05T23:01:51ZYusuke Endohmame@ruby-lang.org
<ul><li><strong>Status</strong> changed from <i>Open</i> to <i>Assigned</i></li><li><strong>Assignee</strong> set to <i>Yutaka HARA</i></li></ul><p>Yhara-san, I&#39;d like your opinion about this ticket.</p>
<h2></h2>
<p>Yusuke Endoh <a href="mailto:mame@tsg.ne.jp">mame@tsg.ne.jp</a></p>
Ruby trunk - Bug #7248: Shouldn't Enumerator::Lazy.new be private?https://bugs.ruby-lang.org/issues/7248?journal_id=345112012-12-08T01:17:35ZYutaka HARA
<ul></ul><p>Hi,</p>
<p>Enumerator::Lazy.new will be needed<br>
(1) when you want to overwrite behavior of a lazy method. eg:</p>
<hr>
<p>class Enumerator::Lazy<br>
def zip(*args, &amp;block)<br>
enums = args.map(&amp;:lazy)<br>
Lazy.new(self){|yielder, val|<br>
ary = [val] + enums.map{|e| e.next}<br>
if block<br>
yielder &lt;&lt; block.call(ary) # make lazy.zip{} behave lazy (currently it doesn&#39;t because enum.zip{} is eager)<br>
else<br>
yielder &lt;&lt; ary<br>
end<br>
}<br>
end<br>
end</p>
<p>fizz = [nil, nil, nil, nil, &quot;Fizz&quot;].cycle<br>
buzz = [nil, nil, &quot;Buzz&quot;].cycle</p>
<a name="p-fizzlazyzipbuzzf-b-fbfirst20"></a>
<h2 >p fizz.lazy.zip(buzz){|f, b| &quot;#{f}#{b}&quot;}.first(20)<a href="#p-fizzlazyzipbuzzf-b-fbfirst20" class="wiki-anchor">&para;</a></h2>
<p>(2) when you want to add a new Enumerable method and its lazy version. eg:</p>
<hr>
<p>module Enumerable<br>
def filter_map(&amp;block)<br>
self.map(&amp;block).compact<br>
end<br>
end</p>
<p>class Enumerator::Lazy<br>
def filter_map(*args, &amp;block)<br>
Lazy.new(self){|yielder, val|<br>
result = block.call(val)<br>
yielder &lt;&lt; result if result<br>
}<br>
end<br>
end</p>
<p>p [11,12,13].filter_map{|i| i*i if i.even?} #=&gt; [144]</p>
<a name="p-1FloatINFINITYlazyfilter_mapi-ii-if-ievenfirst20"></a>
<h2 >p (1..Float::INFINITY).lazy.filter_map{|i| i*i if i.even?}.first(20)<a href="#p-1FloatINFINITYlazyfilter_mapi-ii-if-ievenfirst20" class="wiki-anchor">&para;</a></h2>
Ruby trunk - Bug #7248: Shouldn't Enumerator::Lazy.new be private?https://bugs.ruby-lang.org/issues/7248?journal_id=345482012-12-09T10:44:38ZMarc-Andre Lafortuneruby-core@marc-andre.ca
<ul></ul><p>Oh, interesting.</p>
<p>I&#39;ll do my best to document it, then.</p>
<p>This leads to more questions, though:</p>
<p>1) Is there a use case for the form without a block?</p>
<p>It&#39;s used internally (before calling lazy_set_method), but other than that I can&#39;t see a good use.</p>
<p>2) Is there a use case for specifying a symbol and arguments?</p>
<p>Again, internally we call lazy_set_method, to the symbol and arguments are only used by inspect, right?</p>
<p>3) Is there a good way to improve the <code>inspect</code> of such a lazy enum?</p>
<pre>p [11,12,13].filter_map{|i| i*i if i.even?} # =&gt; #&lt;Enumerator::Lazy: #&lt;Enumerator::Lazy: [1, 2, 3]&gt;:each&gt;
</pre>
<p>Notice the <code>each</code> and no appearance of <code>filter_map</code></p>
<p>Doing <code>Lazy.new(self, :filter_map)</code> does not work and seems redundant.</p>
<p>Thanks</p>
<p>BTW, ultimately, I&#39;m trying to see if Lazy.new can be adapted to accept a <code>size</code> lambda argument...</p>
Ruby trunk - Bug #7248: Shouldn't Enumerator::Lazy.new be private?https://bugs.ruby-lang.org/issues/7248?journal_id=353642013-01-12T04:58:34ZYutaka HARA
<ul><li><strong>Assignee</strong> changed from <i>Yutaka HARA</i> to <i>Marc-Andre Lafortune</i></li></ul><p>marcandre (Marc-Andre Lafortune) wrote:</p>
<blockquote>
<p>Oh, interesting.</p>
<p>I&#39;ll do my best to document it, then.</p>
<p>Thanks!</p>
<p>This leads to more questions, though:</p>
<p>1) Is there a use case for the form without a block?</p>
<p>It&#39;s used internally (before calling lazy_set_method), but other than that I can&#39;t see a good use.</p>
</blockquote>
<p>That form is only for internal use.<br>
You can remove the form without a block by replacing <code>Lazy.new(enum)&#39;<br>
with</code>Lazy.new(enum){|y, v| y&lt;&lt;v}&#39;.</p>
<blockquote>
<p>2) Is there a use case for specifying a symbol and arguments?</p>
</blockquote>
<p>Actually I did not know lazy_initialize can take a symbol :-p<br>
So I&#39;m not sure about how the symbol and arguments are used,<br>
but it looks like for internal use.<br>
According to svn annotate, it is introduced to implement lazy.cycle (<a class="changeset" title="* enumerator.c (lazy_cycle): add Enumerable::Lazy#cycle." href="https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/35028">r35028</a>).</p>
<blockquote>
<p>Again, internally we call lazy_set_method, to the symbol and arguments are only used by inspect, right?</p>
</blockquote>
<p>That seems right.</p>
<blockquote>
<p>3) Is there a good way to improve the <code>inspect</code> of such a lazy enum?</p>
<pre>p [11,12,13].filter_map{|i| i*i if i.even?} # =&gt; #&lt;Enumerator::Lazy: #&lt;Enumerator::Lazy: [1, 2, 3]&gt;:each&gt;
</pre>
<p>Notice the <code>each</code> and no appearance of <code>filter_map</code></p>
<p>Doing <code>Lazy.new(self, :filter_map)</code> does not work and seems redundant.</p>
<p>BTW, ultimately, I&#39;m trying to see if Lazy.new can be adapted to accept a <code>size</code> lambda argument...</p>
</blockquote>
<p>Well, I have no idea. It would be difficult to design Lazy.new which may take <br>
obj, block, symbol, args and size/size_fn...</p>
<p>BTW, I have a question. Document of to_enum says &quot;see Enumerator#size=&quot; but there is no such method. Is it a typo?<br>
<a href="https://github.com/ruby/ruby/blob/e90ccd3beb0b9bf1125461ef68943578739bebbe/enumerator.c#L201">https://github.com/ruby/ruby/blob/e90ccd3beb0b9bf1125461ef68943578739bebbe/enumerator.c#L201</a></p>
Ruby trunk - Bug #7248: Shouldn't Enumerator::Lazy.new be private?https://bugs.ruby-lang.org/issues/7248?journal_id=353972013-01-14T13:59:15ZMarc-Andre Lafortuneruby-core@marc-andre.ca
<ul><li><strong>Priority</strong> changed from <i>Normal</i> to <i>5</i></li></ul><p>Thanks for the answers.</p>
<p>So, the public API of Lazy.new has the following issues:</p>
<ul>
<li>not documented</li>
<li>should require a block but doesn&#39;t</li>
<li>accepts a method name &amp; arguments which aren&#39;t really usable</li>
<li>has a misleading &quot;inspect&quot;</li>
</ul>
<p>Moreover, the only way for the user to create a lazy enumerator with a size is to subclass Lazy.</p>
<p>Here&#39;s what I propose as the official Lazy.new documentation and API:</p>
<pre>Lazy.new(obj, size=nil) { |yielder, *values| ... }
Creates a new Lazy enumerator. When the enumerator is actually enumerated
(e.g. by calling #force), +obj+ will be enumerated and each value passed
to the given block. The block can yield values back using +yielder+.
For example, to create a method +filter_map+ in both lazy and
non-lazy fashions:
module Enumerable
def filter_map(&amp;block)
map(&amp;block).compact
end
end
class Enumerator::Lazy
def filter_map
Lazy.new(self) do |yielder, *values|
result = yield *values
yielder &lt;&lt; result if result
end
end
end
(1..Float::INFINITY).lazy.filter_map{|i| i*i if i.even?}.first(5)
# =&gt; [4, 16, 36, 64, 100]
</pre>
<p>Does this seem acceptable to you?</p>
<p>We should also change the result of the &#39;inspect&#39; method for these user created lazy enumerators to remove the &#39;each&#39;, i.e:</p>
<pre>(1..Float::INFINITY).lazy.filter_map{|i| i*i if i.even?}.inspect
# =&gt; #&lt;Enumerator::Lazy: #&lt;Enumerator::Lazy: 1..Infinity&gt;&gt;
</pre>
<p>If we want to provide an easy way to provide more info in the inspect, we could add extra parameters, but they shouldn&#39;t be used when iterating, only for inspection...</p>
<blockquote>
<p>BTW, I have a question. Document of to_enum says &quot;see Enumerator#size=&quot; but there is no such method. Is it a typo?</p>
</blockquote>
<p>Typo fixed, thanks.</p>
Ruby trunk - Bug #7248: Shouldn't Enumerator::Lazy.new be private?https://bugs.ruby-lang.org/issues/7248?journal_id=353982013-01-14T13:59:37ZMarc-Andre Lafortuneruby-core@marc-andre.ca
<ul><li><strong>Assignee</strong> changed from <i>Marc-Andre Lafortune</i> to <i>Yutaka HARA</i></li></ul> Ruby trunk - Bug #7248: Shouldn't Enumerator::Lazy.new be private?https://bugs.ruby-lang.org/issues/7248?journal_id=354482013-01-17T00:10:06ZYutaka HARA
<ul><li><strong>Assignee</strong> changed from <i>Yutaka HARA</i> to <i>Marc-Andre Lafortune</i></li></ul><p>marcandre (Marc-Andre Lafortune) wrote:</p>
<blockquote>
<p>Here&#39;s what I propose as the official Lazy.new documentation and API:</p>
<pre>Lazy.new(obj, size=nil) { |yielder, *values| ... }
Creates a new Lazy enumerator. When the enumerator is actually enumerated
(e.g. by calling #force), +obj+ will be enumerated and each value passed
to the given block. The block can yield values back using +yielder+.
For example, to create a method +filter_map+ in both lazy and
non-lazy fashions:
module Enumerable
def filter_map(&amp;block)
map(&amp;block).compact
end
end
class Enumerator::Lazy
def filter_map
Lazy.new(self) do |yielder, *values|
result = yield *values
yielder &lt;&lt; result if result
end
end
end
(1..Float::INFINITY).lazy.filter_map{|i| i*i if i.even?}.first(5)
# =&gt; [4, 16, 36, 64, 100]
</pre>
<p>Does this seem acceptable to you?</p>
<p>Yes!</p>
</blockquote>
Ruby trunk - Bug #7248: Shouldn't Enumerator::Lazy.new be private?https://bugs.ruby-lang.org/issues/7248?journal_id=358442013-02-05T12:49:42ZMarc-Andre Lafortuneruby-core@marc-andre.ca
<ul><li><strong>Status</strong> changed from <i>Assigned</i> to <i>Closed</i></li><li><strong>% Done</strong> changed from <i>0</i> to <i>100</i></li></ul><p>This issue was solved with changeset <a class="changeset" title="* enumerator.c: Finalize and document Lazy.new. [Bug #7248] Add Lazy#to_enum and simplify Lazy#..." href="https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39057">r39057</a>.<br>
Marc-Andre, thank you for reporting this issue.<br>
Your contribution to Ruby is greatly appreciated.<br>
May Ruby be with you.</p>
<hr>
<ul>
<li><p>enumerator.c: Finalize and document Lazy.new. [Bug <a class="issue tracker-1 status-5 priority-4 priority-default closed" title="Shouldn&#39;t Enumerator::Lazy.new be private? (Closed)" href="https://bugs.ruby-lang.org/issues/7248">#7248</a>]<br>
Add Lazy#to_enum and simplify Lazy#size.</p></li>
<li><p>test/ruby/test_lazy_enumerator.rb: tests for above</p></li>
</ul>