https://redmine.ruby-lang.org/https://redmine.ruby-lang.org/favicon.ico?17113305112019-10-18T13:58:00ZRuby Issue Tracking SystemRuby master - Feature #16261: Enumerable#each_splat and Enumerator#splathttps://redmine.ruby-lang.org/issues/16261?journal_id=821752019-10-18T13:58:00Zshevegen (Robert A. Heiler)shevegen@gmail.com
<ul></ul><p>Hmmmm.</p>
<p>A slight issue I see with the name "tuple", and then the implicit name addition<br>
".each_tuple", which would then (indirectly) elevate the term tuple.</p>
<p>I know the word tuple from e. g. using tuple in python, but I much prefer ruby's<br>
way to name things (not only because I used ruby for a longer time than python,<br>
but because I think the names in ruby make more sense in general e. g. Array/Hashes<br>
versus List/Dictionaries).</p>
<p>I am not sure if we have "tuples" in ruby core/stdlib yet. I did however google<br>
and find it in Rinda ... so at the least Rinda in stdlib has tuples. :P<br>
<a href="https://ruby-doc.org/stdlib-2.6.5/libdoc/rinda/rdoc/Rinda/Tuple.html" class="external">https://ruby-doc.org/stdlib-2.6.5/libdoc/rinda/rdoc/Rinda/Tuple.html</a><br>
(Not sure about ruby core, though.)</p>
<p>There is also a slight issue with intrinsic complexity (in my opinion), but this<br>
is a lot due to one's personal style and preferences, so I will not comment<br>
much on that part - some ruby users prefer simplicity, others prefer more<br>
flexibility in usage (aka more complex use cases). But I think the name itself<br>
should be considered as well; for the use of .each_tuple, ruby users would<br>
first have to understand what a tuple is. Compare this to e. g. .each_pair<br>
which is a LOT simpler to understand even to genuinely new people. I also<br>
admit that this is not a very strong argument per se, since we have other<br>
variants of .each* already, such as .each_with_index - but I still think<br>
we should be careful which .each* variants are added. I also have no<br>
alternative name proposal, my apologies.</p> Ruby master - Feature #16261: Enumerable#each_splat and Enumerator#splathttps://redmine.ruby-lang.org/issues/16261?journal_id=821782019-10-18T20:26:43Zshan (Shannon Skipper)
<ul></ul><p>This reminds me of a neat post showing applicatives in pictures: <a href="http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html#applicatives" class="external">http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html#applicatives</a></p>
<p>In Haskell:</p>
<pre><code>[(*2), (+3)] <*> [1, 2, 3]
#=> [2,4,6,4,5,6]
</code></pre>
<p>Or with this proposal in Ruby:</p>
<pre><code>[2.:*, 3.:+].product([1, 2, 3]).each_splat.map(&:call)
#=> [2, 4, 6, 4, 5, 6]
</code></pre> Ruby master - Feature #16261: Enumerable#each_splat and Enumerator#splathttps://redmine.ruby-lang.org/issues/16261?journal_id=822342019-10-22T13:44:41ZDan0042 (Daniel DeLorme)
<ul></ul><p>It's worth pointing out the desired difference with regards to lambdas a bit more explicitly:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">].</span><span class="nf">zip</span><span class="p">([</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]).</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="p">:</span><span class="o">+</span><span class="p">)</span> <span class="c1"># ArgumentError (wrong number of arguments (given 0, expected 1))</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">].</span><span class="nf">zip</span><span class="p">([</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]).</span><span class="nf">each_tuple</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="p">:</span><span class="o">+</span><span class="p">)</span> <span class="c1"># => [5, 7, 9]</span>
</code></pre>
<p>But in that case it seems to me the behavior you want is the <em>opposite</em> of a tuple. Where a tuple is a struct-like set of n elements like <code>[1, 4]</code>, what you want here is to destructure that tuple in order to pass each element as an argument of a lambda. So it should be called maybe <code>each_splat</code> and the <em>inverse</em> operation would be called <code>each_tuple</code>.</p>
<p>Or how about something like this based on Enumerator? (hopefully without the hacks)</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Enumerator</span>
<span class="k">def</span> <span class="nf">splat</span>
<span class="k">return</span> <span class="n">to_enum</span><span class="p">(</span><span class="ss">:splat</span><span class="p">)</span> <span class="k">unless</span> <span class="nb">block_given?</span>
<span class="c1">#each{ |item| yield(*item) } #this doesn't always work</span>
<span class="n">each</span><span class="p">{</span> <span class="o">|</span><span class="n">first</span><span class="p">,</span><span class="o">*</span><span class="n">rest</span><span class="o">|</span> <span class="k">yield</span><span class="p">(</span><span class="n">first</span><span class="p">,</span><span class="o">*</span><span class="n">rest</span><span class="p">)</span> <span class="p">}</span> <span class="c1">#hacky solution</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">tuple</span>
<span class="k">return</span> <span class="n">to_enum</span><span class="p">(</span><span class="ss">:tuple</span><span class="p">)</span> <span class="k">unless</span> <span class="nb">block_given?</span>
<span class="c1">#each{ |*item| yield(item) } #this doesn't always work</span>
<span class="n">each</span><span class="p">{</span> <span class="o">|</span><span class="n">first</span><span class="p">,</span><span class="o">*</span><span class="n">rest</span><span class="o">|</span> <span class="k">yield</span><span class="p">([</span><span class="n">first</span><span class="p">,</span><span class="o">*</span><span class="n">rest</span><span class="p">])</span> <span class="p">}</span> <span class="c1">#hacky solution</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">pairs</span> <span class="o">=</span> <span class="p">[</span><span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">30</span><span class="p">].</span><span class="nf">zip</span><span class="p">([</span><span class="mi">10</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="mi">20</span><span class="p">])</span>
<span class="n">pairs</span><span class="p">.</span><span class="nf">each</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:to_s</span><span class="p">)</span> <span class="c1">#=> ["[10, 10]", "[20, 16]", "[30, 20]"]</span>
<span class="n">pairs</span><span class="p">.</span><span class="nf">each</span><span class="p">.</span><span class="nf">tuple</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:to_s</span><span class="p">)</span> <span class="c1">#=> ["[10, 10]", "[20, 16]", "[30, 20]"]</span>
<span class="n">pairs</span><span class="p">.</span><span class="nf">each</span><span class="p">.</span><span class="nf">splat</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:to_s</span><span class="p">)</span> <span class="c1">#=> ["10", "14", "1a"]</span>
<span class="sx">%i[a b c]</span><span class="p">.</span><span class="nf">each_with_index</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:inspect</span><span class="p">)</span> <span class="c1"># ArgumentError (wrong number of arguments (given 1, expected 0))</span>
<span class="sx">%i[a b c]</span><span class="p">.</span><span class="nf">each_with_index</span><span class="p">.</span><span class="nf">tuple</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:inspect</span><span class="p">)</span> <span class="c1"># => ["[:a, 0]", "[:b, 1]", "[:c, 2]"]</span>
<span class="sx">%i[a b c]</span><span class="p">.</span><span class="nf">each_with_index</span><span class="p">.</span><span class="nf">splat</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:inspect</span><span class="p">)</span> <span class="c1"># ArgumentError (wrong number of arguments (given 1, expected 0))</span>
</code></pre> Ruby master - Feature #16261: Enumerable#each_splat and Enumerator#splathttps://redmine.ruby-lang.org/issues/16261?journal_id=822352019-10-22T14:10:20Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://redmine.ruby-lang.org/users/11019">@Dan0042 (Daniel DeLorme)</a> super-good points, thanks!</p>
<p>I'd say that <code>Enumerable#each_tuple</code>/<code>Enumerable#each_splat</code> + <code>Enumerator#tuple</code>/<code>Enumerator#splat</code> is a most powerful and straightforward combination.</p> Ruby master - Feature #16261: Enumerable#each_splat and Enumerator#splathttps://redmine.ruby-lang.org/issues/16261?journal_id=822432019-10-22T18:01:41ZDan0042 (Daniel DeLorme)
<ul></ul><p>Note that <code>each{ |*item| yield(item) }</code> doesn't work because of <a class="issue tracker-2 status-5 priority-4 priority-default closed" title="Feature: Remove exceptional treatment of *foo when it is the sole block parameter (Closed)" href="https://redmine.ruby-lang.org/issues/16166">#16166</a>.</p> Ruby master - Feature #16261: Enumerable#each_splat and Enumerator#splathttps://redmine.ruby-lang.org/issues/16261?journal_id=822472019-10-22T19:33:35ZEregon (Benoit Daloze)
<ul></ul><p>FYI there is Enumerable#each_entry:</p>
<blockquote>
<p>Calls block once for each element in self, passing that<br>
element as a parameter, converting multiple values from yield to an array.</p>
</blockquote>
<p>I think many methods already yield multiple arguments rather than an Array of arguments, <code>zip</code> being one of the exception.<br>
So I'm not sure in how many cases such a method would be useful.</p> Ruby master - Feature #16261: Enumerable#each_splat and Enumerator#splathttps://redmine.ruby-lang.org/issues/16261?journal_id=822492019-10-22T20:52:48ZDan0042 (Daniel DeLorme)
<ul></ul><p><a class="user active user-mention" href="https://redmine.ruby-lang.org/users/772">@Eregon (Benoit Daloze)</a> Thank you very much for the enlightenment!</p>
<p>That means the code above could be rewritten like this. And at that point it's doubtful if <code>tuple</code> is even needed.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Enumerator</span>
<span class="k">def</span> <span class="nf">splat</span>
<span class="k">return</span> <span class="n">to_enum</span><span class="p">(</span><span class="ss">:splat</span><span class="p">)</span> <span class="k">unless</span> <span class="nb">block_given?</span>
<span class="n">each_entry</span><span class="p">{</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span> <span class="k">yield</span><span class="p">(</span><span class="o">*</span><span class="n">item</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">tuple</span>
<span class="k">return</span> <span class="n">to_enum</span><span class="p">(</span><span class="ss">:tuple</span><span class="p">)</span> <span class="k">unless</span> <span class="nb">block_given?</span>
<span class="n">each_entry</span><span class="p">{</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span> <span class="k">yield</span><span class="p">(</span><span class="no">Array</span><span class="o">===</span><span class="n">item</span> <span class="p">?</span> <span class="n">item</span> <span class="p">:</span> <span class="p">[</span><span class="n">item</span><span class="p">])</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre> Ruby master - Feature #16261: Enumerable#each_splat and Enumerator#splathttps://redmine.ruby-lang.org/issues/16261?journal_id=822522019-10-22T22:48:29Zduerst (Martin Dürst)duerst@it.aoyama.ac.jp
<ul><li><strong>Related to</strong> <i><a class="issue tracker-2 status-2 priority-4 priority-default" href="/issues/4539">Feature #4539</a>: Array#zip_with</i> added</li></ul> Ruby master - Feature #16261: Enumerable#each_splat and Enumerator#splathttps://redmine.ruby-lang.org/issues/16261?journal_id=822542019-10-22T22:48:48Zduerst (Martin Dürst)duerst@it.aoyama.ac.jp
<ul><li><strong>Related to</strong> <i><a class="issue tracker-2 status-6 priority-4 priority-default closed" href="/issues/5044">Feature #5044</a>: #zip with block return mapped results</i> added</li></ul> Ruby master - Feature #16261: Enumerable#each_splat and Enumerator#splathttps://redmine.ruby-lang.org/issues/16261?journal_id=822552019-10-22T22:52:03Zduerst (Martin Dürst)duerst@it.aoyama.ac.jp
<ul></ul><p>Dan0042 (Daniel DeLorme) wrote:</p>
<blockquote>
<p>It's worth pointing out the desired difference with regards to lambdas a bit more explicitly:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">].</span><span class="nf">zip</span><span class="p">([</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]).</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="p">:</span><span class="o">+</span><span class="p">)</span> <span class="c1"># ArgumentError (wrong number of arguments (given 0, expected 1))</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">].</span><span class="nf">zip</span><span class="p">([</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]).</span><span class="nf">each_tuple</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="p">:</span><span class="o">+</span><span class="p">)</span> <span class="c1"># => [5, 7, 9]</span>
</code></pre>
</blockquote>
<p>What you want to do here is in many other languages done with <code>zip_with</code>:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">].</span><span class="nf">zip_with</span><span class="p">([</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span> <span class="p">:</span><span class="o">+</span><span class="p">)</span> <span class="c1"># => [5, 7, 9]</span>
</code></pre>
<p>There is already an issue for this, issue <a class="issue tracker-2 status-2 priority-4 priority-default" title="Feature: Array#zip_with (Assigned)" href="https://redmine.ruby-lang.org/issues/4539">#4539</a>, which is open and waits for Matz's approval.</p> Ruby master - Feature #16261: Enumerable#each_splat and Enumerator#splathttps://redmine.ruby-lang.org/issues/16261?journal_id=822642019-10-23T07:33:21Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://redmine.ruby-lang.org/users/50">@duerst (Martin Dürst)</a></p>
<blockquote>
<p>What you want to do here is in many other languages done with <code>zip_with</code></p>
</blockquote>
<p>I used <code>zip</code> only as a simplest way to construct an example. In our current codebase we have a fare share of internal methods defined with two pairs of braces, like <code>def similar?((word1, word2))</code>, because this allows us, for example, to say things like (imagine calculating some diffs):</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">diff_pairs</span><span class="p">.</span><span class="nf">reject</span><span class="p">(</span><span class="o">&</span><span class="nb">method</span><span class="p">(</span><span class="ss">:similar?</span><span class="p">)).</span><span class="nf">select</span><span class="p">(</span><span class="o">&</span><span class="nb">method</span><span class="p">(</span><span class="ss">:same_paragraph?</span><span class="p">)).</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="nb">method</span><span class="p">(</span><span class="ss">:calculate_closeness</span><span class="p">))</span>
</code></pre>
<p>In other places, we are still just rely on <code>map { |foo, bar, baz|</code>, <code>select</code> and so on.</p>
<p>A very small amount of initial data of such chains is produced with <code>zip</code>, but even when it is, <code>zip_with</code> can't help with select/reject/group_by and other Enumerable methods.</p> Ruby master - Feature #16261: Enumerable#each_splat and Enumerator#splathttps://redmine.ruby-lang.org/issues/16261?journal_id=823412019-10-26T11:06:46Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/82341/diff?detail_id=55473">diff</a>)</li></ul> Ruby master - Feature #16261: Enumerable#each_splat and Enumerator#splathttps://redmine.ruby-lang.org/issues/16261?journal_id=823422019-10-26T11:07:07Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul><li><strong>Subject</strong> changed from <i>Enumerable#each_tuple</i> to <i>Enumerable#each_splat and Enumerator#splat</i></li></ul> Ruby master - Feature #16261: Enumerable#each_splat and Enumerator#splathttps://redmine.ruby-lang.org/issues/16261?journal_id=828482019-11-28T06:24:59Zknu (Akinori MUSHA)knu@ruby-lang.org
<ul></ul><p>I agree this feature would be a nice addition.</p>
<p>Actually I had exactly the same idea, presented at Rails Developer Meetup 2019: <a href="https://www.slideshare.net/akinorimushaevolution-of-enumerator" class="external">https://www.slideshare.net/akinorimushaevolution-of-enumerator</a> (Japanese)</p>
<p>There's a subtle difference between Hash#each/map and Hash#select/reject in how they yield each key-value pair.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="p">{</span><span class="n">a</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span><span class="n">b</span><span class="p">:</span><span class="mi">2</span><span class="p">}.</span><span class="nf">select</span><span class="p">{</span><span class="o">|</span><span class="n">x</span><span class="o">|</span><span class="nb">p</span> <span class="n">x</span><span class="p">}</span>
<span class="c1"># :a</span>
<span class="c1"># :b</span>
<span class="p">{</span><span class="n">a</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span><span class="n">b</span><span class="p">:</span><span class="mi">2</span><span class="p">}.</span><span class="nf">each</span><span class="p">{</span><span class="o">|</span><span class="n">x</span><span class="o">|</span><span class="nb">p</span> <span class="n">x</span><span class="p">}</span>
<span class="c1"># [:a, 1]</span>
<span class="c1"># [:b, 2]</span>
</code></pre>
<p>I guess this was an unintended difference, but we cannot fix it by now for compatibility reasons, and each_splat would be one way to work around it.</p> Ruby master - Feature #16261: Enumerable#each_splat and Enumerator#splathttps://redmine.ruby-lang.org/issues/16261?journal_id=828562019-11-28T08:15:50Zmatz (Yukihiro Matsumoto)matz@ruby.or.jp
<ul><li><strong>Status</strong> changed from <i>Open</i> to <i>Rejected</i></li></ul><p>As far as I understand, the code with the proposal <code>[1, 2, 3].zip([4, 5, 6]).each_tuple.map(&:+)</code> can be written as following with numbered parameters:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">].</span><span class="nf">zip</span><span class="p">([</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]).</span><span class="nf">map</span><span class="p">{</span><span class="n">_1</span> <span class="o">+</span> <span class="n">_2</span><span class="p">}</span>
</code></pre>
<p>which is quite plain and shorter. So I reject the idea for the time being. Maybe we will revisit the idea once we re-introduce the method reference operator in the future.</p>
<p>Matz.</p>