Ruby Issue Tracking System: Issueshttps://bugs.ruby-lang.org/https://bugs.ruby-lang.org/favicon.ico?17113305112024-03-05T15:21:08ZRuby Issue Tracking System
Redmine Ruby master - Bug #20325 (Open): Enumerator.product.size bug with zero * infinite enumeratorshttps://bugs.ruby-lang.org/issues/203252024-03-05T15:21:08Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Enumerator</span><span class="p">.</span><span class="nf">product</span><span class="p">([],</span> <span class="mi">1</span><span class="o">..</span><span class="p">).</span><span class="nf">to_a</span> <span class="c1"># => [] (OK)</span>
<span class="no">Enumerator</span><span class="p">.</span><span class="nf">product</span><span class="p">([],</span> <span class="mi">1</span><span class="o">..</span><span class="p">).</span><span class="nf">size</span> <span class="c1"># => Infinity (Should be 0)</span>
</code></pre> Ruby master - Bug #18018 (Closed): Float#floor / truncate sometimes result that is too small.https://bugs.ruby-lang.org/issues/180182021-07-01T22:53:33Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="mf">291.4</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># => 291.4 (ok)</span>
<span class="mf">291.4</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="c1"># => 291.39 (not ok)</span>
<span class="mf">291.4</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="c1"># => 291.4 (ok)</span>
<span class="mf">291.4</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span> <span class="c1"># => 291.4 (ok)</span>
<span class="mf">291.4</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="c1"># => 291.39999 (not ok)</span>
<span class="mf">291.4</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span> <span class="c1"># => 291.4 (ok)</span>
</code></pre>
<p><code>g = f.floor(n)</code>, for <code>n > 0</code> must return the highest float that has the correct properties:</p>
<ul>
<li>
<code>g</code> <= <code>f</code>
</li>
<li>
<code>g</code>'s decimal string representation has at most <code>n</code> digits</li>
</ul>
<p>I'll note that <code>floor</code> should be stable, i.e. <code>f.floor(n).floor(n) == f.floor(n)</code> for all <code>f</code> and <code>n</code>.</p>
<p>Same idea for <code>truncate</code>, except for negative numbers (where <code>(-f).truncate(n) == -(f.floor(n))</code> for positive <code>f</code>).</p>
<p>Noticed by Eustáquio Rangel but posted on the mailing list.</p>
<p>Please do not reply that I need to learn how floats work. Note that example given in doc <code>(0.3/0.1).floor == 2</code> is not this issue, since <code>0.3/0.1 #=> 2.9999999999999996</code></p> Ruby master - Feature #17785 (Open): Allow named parameters to be keywordshttps://bugs.ruby-lang.org/issues/177852021-04-08T15:08:40Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>We should allow named parameters to be keywords and use add a trailing <code>_</code> to the corresponding variable:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="n">arg</span><span class="p">,</span> <span class="k">class</span><span class="p">:)</span>
<span class="n">arg</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="n">class_</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">check</span><span class="p">(</span><span class="mi">42</span><span class="p">,</span> <span class="ss">class: </span><span class="no">Integer</span><span class="p">)</span> <span class="c1"># => true</span>
</code></pre>
<p>Currently, if we want such an API we have to use <code>**rest</code>:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">rest</span><span class="p">)</span>
<span class="n">class_</span> <span class="o">=</span> <span class="n">rest</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="ss">:class</span><span class="p">)</span> <span class="p">{</span> <span class="k">raise</span> <span class="no">ArgumentError</span><span class="p">(</span><span class="s1">'missing keyword: :class'</span><span class="p">)}</span>
<span class="k">if</span> <span class="n">rest</span><span class="p">.</span><span class="nf">size</span> <span class="o">></span> <span class="mi">1</span>
<span class="n">unknown</span> <span class="o">=</span> <span class="n">rest</span><span class="p">.</span><span class="nf">keys</span> <span class="o">-</span> <span class="p">[</span><span class="ss">:class</span><span class="p">]</span>
<span class="k">raise</span> <span class="no">ArgumentError</span><span class="p">(</span><span class="s2">"unknown keyword(s): :</span><span class="si">#{</span><span class="n">unknown</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s1">', :'</span><span class="p">)</span><span class="si">}</span><span class="s2">)
end
arg.is_a?(class_)
end
</span></code></pre>
<p>This is very verbose, much less convenient, much less readable, prevents <code>steep</code> from generating the proper signature, etc.</p>
<p>We should do the same for pattern match.</p> Ruby master - Bug #17600 (Closed): lib/benchmark should use `$stdout` instead of `STDOUT`https://bugs.ruby-lang.org/issues/176002021-01-31T17:09:39Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Unless there is objection, I'll change all uses of <code>STDOUT</code> to <code>$stdout</code> in <code>lib/benchmark</code>, to allow redirection to other io.</p>
<p>See <a href="https://github.com/ruby/benchmark/issues/5" class="external">https://github.com/ruby/benchmark/issues/5</a></p> Ruby master - Bug #17577 (Closed): Segfault when sending some Exceptions to a Ractorhttps://bugs.ruby-lang.org/issues/175772021-01-24T19:03:45Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>The following segfaults:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">r</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="n">receive</span> <span class="p">}</span>
<span class="k">begin</span>
<span class="n">foo</span> <span class="c1"># => raises a NoMethodError</span>
<span class="k">rescue</span> <span class="no">Exception</span> <span class="o">=></span> <span class="n">e</span>
<span class="n">r</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
<span class="k">end</span>
<span class="nb">p</span> <span class="n">r</span><span class="p">.</span><span class="nf">take</span>
</code></pre>
<p>Note that some exceptions are ok:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">raise</span> <span class="c1"># => ok</span>
<span class="k">raise</span> <span class="no">NoMethodError</span><span class="p">,</span> <span class="s1">'...'</span> <span class="c1"># => ok</span>
<span class="s1">'s'</span><span class="p">.</span><span class="nf">to_i</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">)</span> <span class="c1"># => ok</span>
<span class="n">foo</span> <span class="c1"># => segfault</span>
<span class="no">Xyz</span> <span class="c1"># => segfault</span>
</code></pre>
<p>Present on 3.0.0p0 as well as master (ruby 3.1.0dev (2021-01-24T14:26:11Z master 30f11e73c4) [x86_64-darwin18])</p>
<pre><code>-- Control frame information -----------------------------------------------
c:0004 p:0003 s:0019 e:000018 METHOD <internal:ractor>:583
c:0003 p:0025 s:0012 e:000011 RESCUE ractor.rb:5
c:0002 p:0025 s:0008 E:000680 EVAL ractor.rb:2 [FINISH]
c:0001 p:0000 s:0003 E:001b80 (none) [FINISH]
-- C level backtrace information -------------------------------------------
/Users/mal/.rbenv/versions/dev/bin/ruby(rb_vm_bugreport+0x732) [0x105de8672]
/Users/mal/.rbenv/versions/dev/bin/ruby(rb_bug_for_fatal_signal+0x1dc) [0x105bf671c]
/Users/mal/.rbenv/versions/dev/bin/ruby(sigsegv+0x5b) [0x105d3c87b]
/usr/lib/system/libsystem_platform.dylib(_sigtramp+0x1d) [0x7fff61425b5d]
[0x34]
/Users/mal/.rbenv/versions/dev/bin/ruby(rb_objspace_reachable_objects_from+0xa3) [0x105c19193]
/Users/mal/.rbenv/versions/dev/bin/ruby(obj_traverse_replace_i+0x38d) [0x105cedb0d]
/Users/mal/.rbenv/versions/dev/bin/ruby(obj_traverse_replace_i+0x46f) [0x105cedbef]
/Users/mal/.rbenv/versions/dev/bin/ruby(ractor_send+0x18b) [0x105ce8fdb]
/Users/mal/.rbenv/versions/dev/bin/ruby(vm_exec_core+0x77ea) [0x105dbfcaa]
/Users/mal/.rbenv/versions/dev/bin/ruby(rb_vm_exec+0xb36) [0x105dd0916]
/Users/mal/.rbenv/versions/dev/bin/ruby(rb_ec_exec_node+0x14d) [0x105c01aad]
/Users/mal/.rbenv/versions/dev/bin/ruby(ruby_run_node+0x57) [0x105c018f7]
/Users/mal/.rbenv/versions/dev/bin/ruby(main+0x71) [0x105b51141]
</code></pre> Ruby master - Misc #17569 (Open): `uri` lib maintainershiphttps://bugs.ruby-lang.org/issues/175692021-01-22T15:48:33Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I’d like to merge <a href="https://github.com/ruby/uri/pull/15" class="external">https://github.com/ruby/uri/pull/15</a> but it is an API change. I would release v1.0.0. <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/271">@akr (Akira Tanaka)</a> is the official maintainer of <code>uri</code>… Is he still interested in this role? Otherwise we could put “Ruby core team” in the listing…</p> Ruby master - Bug #17543 (Closed): Ractor isolation broken by `self` in shareable prochttps://bugs.ruby-lang.org/issues/175432021-01-15T07:39:58Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Discussing with <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/8409">@MaxLap (Maxime Lapointe)</a> we realized that the <code>self</code> in a shareable proc is not properly isolated:</p>
<pre><code>class Foo
attr_accessor :x
def pr
Ractor.make_shareable(Proc.new { self })
end
end
f = Foo.new
f.x = [1, 2, 3]
Ractor.new(f.pr) { |pr| pr.call.x << :oops }
p f.x # => [1, 2, 3, :oops]
</code></pre>
<p>If the <code>self</code> refers to a shareable object then it's fine, but for non-shareable objects it has to be reset to <code>nil</code> or to a global shareable object that would have an instructive <code>inspect</code>.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Ractor</span><span class="o">::</span><span class="no">DETACHED_SELF</span> <span class="o">=</span> <span class="no">Object</span><span class="p">.</span><span class="nf">new</span>
<span class="k">def</span> <span class="nf"><<</span> <span class="no">Ractor</span><span class="o">::</span><span class="no">DETACHED_SELF</span>
<span class="k">def</span> <span class="nf">inspect</span>
<span class="s1">'<#detached self>'</span>
<span class="k">end</span>
<span class="k">alias</span> <span class="nb">to_s</span> <span class="nb">inspect</span>
<span class="k">end</span>
<span class="no">Ractor</span><span class="o">::</span><span class="no">DETACHED_SELF</span><span class="p">.</span><span class="nf">freeze</span>
</code></pre> Ruby master - Bug #17531 (Closed): `did_you_mean` not Ractor friendlyhttps://bugs.ruby-lang.org/issues/175312021-01-12T19:30:56Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>The gem <code>did_you_mean</code> uses a class instance variable that does not work with Ractor.</p>
<pre><code>$ RUBYOPT='--disable-did_you_mean' ruby -W0 -e 'Ractor.new{ begin ; nil + 42; rescue Exception => e; e.to_s end}.take'
# => prints nothing (ok)
$ ruby -W0 -e 'Ractor.new{ begin ; nil + 42; rescue Exception => e; e.to_s end}.take'
# Prints
/Users/mal/.rbenv/versions/3.0.0/lib/ruby/3.0.0/did_you_mean.rb:102:in `formatter'
/Users/mal/.rbenv/versions/3.0.0/lib/ruby/3.0.0/did_you_mean/core_ext/name_error.rb:9:in `to_s'
-e:1:in `rescue in block in <main>'
-e:1:in `block in <main>'
# (not ok)
</code></pre> Ruby master - Feature #17513 (Open): Methods of shareable objects and UnboundMethods should be sh...https://bugs.ruby-lang.org/issues/175132021-01-06T06:04:18Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Foo</span>
<span class="k">def</span> <span class="nf">foo</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">f</span> <span class="o">=</span> <span class="no">Foo</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">freeze</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">shareable?</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="c1"># => true</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">make_shareable</span><span class="p">(</span><span class="n">f</span><span class="p">.</span><span class="nf">method</span><span class="p">(</span><span class="ss">:foo</span><span class="p">).</span><span class="nf">to_proc</span><span class="p">)</span> <span class="c1"># => Proc, ok</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">make_shareable</span><span class="p">(</span><span class="n">f</span><span class="p">.</span><span class="nf">method</span><span class="p">(</span><span class="ss">:foo</span><span class="p">))</span> <span class="c1"># => Ractor::Error, expected Method</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">make_shareable</span><span class="p">(</span><span class="no">Foo</span><span class="p">.</span><span class="nf">instance_method</span><span class="p">(</span><span class="ss">:foo</span><span class="p">))</span> <span class="c1"># => Ractor::Error, expected UnboundMethod</span>
</code></pre> Ruby master - Bug #17506 (Open): Ractor isolation broken by ThreadGrouphttps://bugs.ruby-lang.org/issues/175062021-01-03T20:05:43Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Ractors currently share the ThreadGroup.</p>
<p>This doesn't seem very useful as there is no possible communication between the Threads of different Ractors.</p>
<p>It is also an isolation error:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">ThreadGroup</span><span class="p">.</span><span class="nf">attr_accessor</span> <span class="ss">:foo</span>
<span class="n">var</span> <span class="o">=</span> <span class="no">Thread</span><span class="p">.</span><span class="nf">current</span><span class="p">.</span><span class="nf">group</span><span class="p">.</span><span class="nf">foo</span> <span class="o">=</span> <span class="p">[</span><span class="ss">:example</span><span class="p">]</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="no">Thread</span><span class="p">.</span><span class="nf">current</span><span class="p">.</span><span class="nf">group</span><span class="p">.</span><span class="nf">foo</span> <span class="o"><<</span> <span class="p">[</span><span class="ss">:oops</span><span class="p">]</span> <span class="p">}.</span><span class="nf">take</span>
<span class="n">var</span> <span class="c1"># => [:example, [:oops]]</span>
</code></pre>
<p>Should <code>Ractor.new</code> create a new <code>ThreadGroup</code>? Should <code>ThreadGroup</code> not have (non-shareable) instance variables? Or should <code>Ractor.new { Thread.current.group }.take</code> be <code>nil</code>? See also <a href="https://bugs.ruby-lang.org/issues/17505" class="external">https://bugs.ruby-lang.org/issues/17505</a> about <code>nil</code>.</p>
<p>Note that <code>Ractor</code> respects the <code>ThreadGroup</code>'s state:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Thread</span><span class="p">.</span><span class="nf">current</span><span class="p">.</span><span class="nf">group</span><span class="p">.</span><span class="nf">enclose</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="no">Thread</span><span class="p">.</span><span class="nf">current</span><span class="p">.</span><span class="nf">group</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="no">Thread</span><span class="p">.</span><span class="nf">current</span><span class="p">)</span> <span class="p">}.</span><span class="nf">take</span> <span class="c1"># => can't move to the enclosed thread group</span>
<span class="no">Thread</span><span class="p">.</span><span class="nf">current</span><span class="p">.</span><span class="nf">group</span><span class="p">.</span><span class="nf">freeze</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="p">{}</span> <span class="c1"># => ThreadError (can't start a new thread (frozen ThreadGroup))</span>
</code></pre>
<p>I am not sure what is the best behavior as I don't have enough experience with how ThreadGroups are used, especially enclosed ThreadGroups.</p> Ruby master - Bug #17505 (Closed): Can `Thread#group` actually be `nil`?https://bugs.ruby-lang.org/issues/175052021-01-03T19:41:27Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Is there a circumstance where <code>Thread#group</code> could actually be <code>nil</code>?</p>
<p>The documentation says so, there seems to be source code for this, but I can find no test or RubySpec for this and I don't see anywhere in the <code>ThreadGroup</code> API that could allow this.</p> Ruby master - Misc #17502 (Open): C vs Rubyhttps://bugs.ruby-lang.org/issues/175022021-01-02T20:25:28Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Some features are coded in a mix of Ruby and C (e.g. ractor.rb).</p>
<p>External gems don't have access to this. The C-API to deal with keyword parameters is also very verbose the parsing and the engine does not know it.</p>
<p>Moreover, some optimization PRs are simply rewriting C-code into Ruby using pseudo C code.</p>
<p>I understand the intentions are great, but changes like <a href="https://github.com/ruby/ruby/pull/4018/files" class="external">https://github.com/ruby/ruby/pull/4018/files</a> seem a symptom that something needs to be improved with the C api.</p>
<pre><code class="diff syntaxhl" data-language="diff"><span class="gd">-static VALUE
- flo_zero_p(VALUE num)
- {
- return flo_iszero(num) ? Qtrue : Qfalse;
- }
</span># in different file:
<span class="gi">+ def zero?
+ Primitive.attr! 'inline'
+ Primitive.cexpr! 'flo_iszero(self) ? Qtrue : Qfalse'
+ end
</span></code></pre>
<p>It seems to me that this is a way to circumvent a deeper issue. Is this the right direction?</p>
<p>Is there a plan for an API that would:</p>
<ol>
<li>be accessible to C extensions</li>
<li>can't be re-written any faster in pseuso-C in Ruby</li>
<li>has an easy way to define keyword parameters?</li>
</ol>
<p>I realize that RBS may give perfect signatures, but ideally <code>parameters</code> would be more informative for C-functions too.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Ractor</span><span class="p">.</span><span class="nf">method</span><span class="p">(</span><span class="ss">:yield</span><span class="p">).</span><span class="nf">parameters</span>
<span class="c1"># => [[:req, :obj], [:key, :move]] # good!</span>
<span class="no">Fiber</span><span class="p">.</span><span class="nf">method</span><span class="p">(</span><span class="ss">:initialize</span><span class="p">).</span><span class="nf">parameters</span>
<span class="c1"># => [[:rest]] # not good, should be [[:key, :blocking]]</span>
</code></pre> Ruby master - Bug #17497 (Closed): Ractor performance issuehttps://bugs.ruby-lang.org/issues/174972020-12-31T21:48:50Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>There's a strange performance issue with Ractor (at least on MacOS, didn't run on other OS).</p>
<p>I ran a benchmark doing 3 different types of work:</p>
<ul>
<li>"fib": method calls (naive fibonacci calculation)</li>
<li>"cpu": <code>(0...1000).inject(:+)</code>
</li>
<li>"sleep": call <code>sleep</code>
</li>
</ul>
<p>I get the kind of results I was excepting for the <code>fib</code> and for sleeping, but the results for the "cpu" workload show a problem.</p>
<p>It is so slow that my pure Ruby backport (using Threads) is 65x faster 😮 on my Mac Pro (despite having 6 cores). Expected results would be 6x slower, so in that case Ractor is 400x slower than it should 😿</p>
<p>On my MacBook (2 cores) the results are not as bad, the <code>cpu</code> workload is 3x faster with my pure-Ruby backport (only) instead of ~2x slower, so the factor is 6x too slow.</p>
<pre><code>$ gem install backports
Successfully installed backports-3.20.0
1 gem installed
$ ruby ractor_test.rb
<internal:ractor>:267: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues.
fib: 110 ms | cpu: 22900 ms | sleep: 206 ms
$ B=t ruby ractor_test.rb
Using pure Ruby implementation
fib: 652 ms | cpu: 337 ms | sleep: 209 ms
</code></pre>
<p>Notice the <code>sleep</code> run takes similar time, which is good, and <code>fib</code> is ~6x faster on my 6-core CPU (and ~2x faster on my 2-core MacBook), again that's good as the pure ruby version uses Threads and thus runs with a single GVL.</p>
<p>The <code>cpu</code> version is the problem.</p>
<p>Script is here: <a href="https://gist.github.com/marcandre/bfed626e538a3d0fc7cad38dc026cf0e" class="external">https://gist.github.com/marcandre/bfed626e538a3d0fc7cad38dc026cf0e</a></p> Ruby master - Bug #17428 (Closed): Method#inspect bad output for class methodshttps://bugs.ruby-lang.org/issues/174282020-12-23T00:35:55Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<pre><code>$ $ ruby -e 'p String.method(:prepend)'
# 2.7.0:
#<Method: String.prepend(*)>
# 3.0.0:
#<Method: #<Class:Object>(Module)#prepend(*)>
</code></pre>
<p>@jeremyevans found it shows the method as pertaining to one level too high (which is good for objects as we don't want to show the singleton class there, but not for classes).</p>
<p>Probably due to <a class="issue tracker-1 status-5 priority-4 priority-default closed" title="Bug: What should be the correct output for Method#inspect with singleton methods? (Closed)" href="https://bugs.ruby-lang.org/issues/15608">#15608</a></p> Ruby master - Feature #17414 (Open): Ractor should allow access to shareable attributes for Modul...https://bugs.ruby-lang.org/issues/174142020-12-21T01:55:02Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Current situation is <em>very</em> limiting.</p>
<p>Use-case: global config.</p>
<p>Example: <a href="https://github.com/ruby/psych/blob/master/lib/psych.rb#L637-L640" class="external">yaml has a global config</a> and it's not clear to me how to make that Ractor-aware (nicely).</p>
<p>It is possible to have the same effect but in ugly ways:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># Using instance variables of Module not allowed:</span>
<span class="k">module</span> <span class="nn">Config</span>
<span class="k">class</span> <span class="o"><<</span> <span class="nb">self</span>
<span class="nb">attr_accessor</span> <span class="ss">:conf</span>
<span class="k">end</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">conf</span> <span class="o">=</span> <span class="mi">42</span>
<span class="k">end</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="no">Config</span><span class="p">.</span><span class="nf">conf</span> <span class="o">=</span> <span class="mi">66</span> <span class="p">}.</span><span class="nf">take</span> <span class="c1"># => can not access instance variables from non-main Ractors</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="nb">puts</span> <span class="no">Config</span><span class="p">.</span><span class="nf">conf</span> <span class="p">}.</span><span class="nf">take</span> <span class="c1"># => can not access instance variables from non-main Ractors</span>
<span class="c1"># Same functionality using constants allowed:</span>
<span class="k">module</span> <span class="nn">Config</span>
<span class="k">class</span> <span class="o"><<</span> <span class="nb">self</span>
<span class="k">def</span> <span class="nf">conf</span>
<span class="no">CONF</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">conf</span><span class="o">=</span><span class="p">(</span><span class="n">new_conf</span><span class="p">)</span>
<span class="n">remove_const</span><span class="p">(</span><span class="ss">:CONF</span><span class="p">)</span>
<span class="nb">const_set</span><span class="p">(</span><span class="ss">:CONF</span><span class="p">,</span> <span class="n">new_conf</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="no">CONF</span> <span class="o">=</span> <span class="mi">42</span>
<span class="k">end</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="no">Config</span><span class="p">.</span><span class="nf">conf</span> <span class="o">=</span> <span class="mi">66</span> <span class="p">}.</span><span class="nf">take</span> <span class="c1"># => ok</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="nb">puts</span> <span class="no">Config</span><span class="p">.</span><span class="nf">conf</span> <span class="p">}.</span><span class="nf">take</span> <span class="c1"># => 66</span>
<span class="c1"># Same functionality using methods allowed:</span>
<span class="k">module</span> <span class="nn">Config</span>
<span class="k">class</span> <span class="o"><<</span> <span class="nb">self</span>
<span class="k">def</span> <span class="nf">conf</span>
<span class="mi">42</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">conf</span><span class="o">=</span><span class="p">(</span><span class="n">new_conf</span><span class="p">)</span>
<span class="n">singleton_class</span><span class="p">.</span><span class="nf">undef_method</span><span class="p">(</span><span class="ss">:conf</span><span class="p">)</span>
<span class="n">define_singleton_method</span><span class="p">(</span><span class="ss">:conf</span><span class="p">,</span> <span class="o">&</span><span class="no">Ractor</span><span class="p">.</span><span class="nf">make_shareable</span><span class="p">(</span><span class="no">Proc</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="n">new_conf</span> <span class="p">}))</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="no">Config</span><span class="p">.</span><span class="nf">conf</span> <span class="o">=</span> <span class="mi">66</span> <span class="p">}.</span><span class="nf">take</span> <span class="c1"># => ok</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="nb">puts</span> <span class="no">Config</span><span class="p">.</span><span class="nf">conf</span> <span class="p">}.</span><span class="nf">take</span> <span class="c1"># => 66</span>
</code></pre>
<p>The priority would be to allow reading these instance variables if they are shareable. Ideally writing would also be allowed, but limiting that to main ractor is less probablematic than with reading.</p> Ruby master - Feature #17406 (Open): Add `NoMatchingPatternError#depth`https://bugs.ruby-lang.org/issues/174062020-12-18T21:45:35Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Could we have <code>NoMatchingPatternError#depth</code>, returning the number of <code>case...in...end</code> an exception has traversed?</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">show_depth</span>
<span class="k">yield</span>
<span class="k">rescue</span> <span class="no">NoMatchingPatternError</span> <span class="o">=></span> <span class="n">e</span>
<span class="nb">puts</span> <span class="s2">"Depth: </span><span class="si">#{</span><span class="n">e</span><span class="p">.</span><span class="nf">depth</span><span class="si">}</span><span class="s2">"</span>
<span class="k">raise</span>
<span class="k">end</span>
<span class="n">show_depth</span> <span class="k">do</span>
<span class="k">case</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="k">in</span> <span class="p">[</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">]</span> <span class="k">then</span>
<span class="n">show_depth</span> <span class="k">do</span>
<span class="n">x</span> <span class="o">=></span> <span class="p">[</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">]</span> <span class="c1"># => raises NoMatchingPatternError</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># Prints "Depth: 0" then "Depth: 1"</span>
</code></pre>
<p>This could help bring pattern match closer to a language construct people can play with.</p>
<p>Example usecase: implement <code>Ractor#receive_if</code> as in <a href="https://bugs.ruby-lang.org/issues/17378#note-6" class="external">https://bugs.ruby-lang.org/issues/17378#note-6</a></p> Ruby master - Feature #17404 (Open): Ractor `move:` API to allow shareability checkhttps://bugs.ruby-lang.org/issues/174042020-12-17T18:29:50Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I'd like to <code>ractor.send(message)</code> and express that <code>message</code> should be shareable. Currently I'm given two choices: <code>move: true</code> and <code>move: false</code> / nothing, neither of which have an effect if my <code>message</code> is shareable, and neither of which will tell me in case there's a bug in my program and <code>message</code> is not shareable.</p>
<p>Could we consider a slightly different API (for 3.0 or 3.1)?</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">ractor</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="ss">pass: :copy</span><span class="p">)</span> <span class="c1"># => like current `move: false`</span>
<span class="n">ractor</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="ss">pass: :move</span><span class="p">)</span> <span class="c1"># => like current `move: true`</span>
<span class="n">ractor</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="ss">pass: :share</span><span class="p">)</span> <span class="c1"># => raise in case message is not shareable</span>
<span class="n">ractor</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="n">message</span><span class="p">)</span> <span class="c1"># => same as `pass: :copy`</span>
</code></pre> Ruby master - Feature #17393 (Open): `Ractor::Moved#inspect`https://bugs.ruby-lang.org/issues/173932020-12-14T20:48:49Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>It could be helpful to define <code>Ractor::Moved#inspect</code> and output the source location of when the data was moved. If preferred, it could raise an error with this information:</p>
<pre><code>x = []
Ractor.new{ receive }.send(x, move: true)
p x # => "Data was moved in `example.rb:4`"
# or
p x # => "Data was moved in `example.rb:4`" (Ractor::MovedError)
</code></pre>
<p>Also <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/710">@zverok (Victor Shepelev)</a> and myself were wondering if there was a technical reason to freeze <code>Ractor::Moved</code>? If not, is it only to "force" people to use refinements (which are allowed on frozen classes)? It's already known that it is in general a bad idea to modify builtin classes, so it's not clear to me that freezing that class is best.</p> Ruby master - Bug #17379 (Closed): Refinement with modules redefinition bughttps://bugs.ruby-lang.org/issues/173792020-12-09T04:21:11Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Depending on the circumstance, a refinement can be modified even after being used:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">foo</span>
<span class="p">[</span><span class="ss">:base</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">module</span> <span class="nn">M</span>
<span class="k">def</span> <span class="nf">foo</span>
<span class="k">super</span> <span class="o"><<</span> <span class="ss">:M</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">module</span> <span class="nn">Ext</span>
<span class="n">refine</span> <span class="no">Object</span> <span class="k">do</span>
<span class="kp">include</span> <span class="no">M</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">using</span> <span class="no">Ext</span>
<span class="nb">p</span> <span class="s1">'asd'</span><span class="p">.</span><span class="nf">foo</span> <span class="k">unless</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'SKIP'</span><span class="p">]</span> <span class="c1"># => [:base, :M] (ok)</span>
<span class="k">module</span> <span class="nn">M</span>
<span class="k">def</span> <span class="nf">foo</span>
<span class="k">super</span> <span class="o"><<</span> <span class="ss">:new_ref</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="nb">p</span> <span class="s1">'asd'</span><span class="p">.</span><span class="nf">foo</span> <span class="c1"># => depends (not ok)</span>
</code></pre>
<p>Running this gives:</p>
<pre><code>$ ruby refinement.rb
[:base, :M]
[:base, :M] # => ok
$ SKIP=t ruby refinement.rb
[:base, :new_ref] # => should be [:base, :M]
</code></pre> Ruby master - Bug #17368 (Closed): `Ractor.select()` loops foreverhttps://bugs.ruby-lang.org/issues/173682020-12-05T07:41:44Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I propose it raises an error in <a href="https://github.com/ruby/ruby/pull/3848" class="external">https://github.com/ruby/ruby/pull/3848</a></p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Ractor</span><span class="p">.</span><span class="nf">select</span> <span class="c1"># => loops</span>
<span class="c1"># after</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">select</span> <span class="c1"># => ArgumentError, "specify at least one ractor or `yield_value`"</span>
</code></pre> Ruby master - Bug #17366 (Closed): Ractor odd issue with timeout + receive + sleep + takehttps://bugs.ruby-lang.org/issues/173662020-12-05T06:58:11Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I believe the following behavior is incorrect:</p>
<pre><code>ruby -r timeout -e 'r = Ractor.new { Timeout.timeout(0.1) { sleep(1) } rescue :timeout }; p r.take'
# => :timeout (ok)
ruby -r timeout -e 'r = Ractor.new { Timeout.timeout(0.1) { sleep(1) } rescue :timeout }; sleep(0.2); p r.take'
# => :timeout (ok)
ruby -r timeout -e 'r = Ractor.new { Timeout.timeout(0.1) { Ractor.receive } rescue :timeout }; p r.take'
# => :timeout (ok)
ruby -r timeout -e 'r = Ractor.new { Timeout.timeout(0.1) { Ractor.receive } rescue :timeout }; sleep(0.2); p r.take'
<internal:ractor>:130:in `take': The outgoing-port is already closed (Ractor::ClosedError) # => not ok
</code></pre> Ruby master - Feature #17363 (Assigned): Timeoutshttps://bugs.ruby-lang.org/issues/173632020-12-03T14:58:16Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Builtin methods like <code>Queue.pop</code> and <code>Ractor.receive</code> have no timeout parameter.</p>
<p>We should either:</p>
<ul>
<li>provide such a parameter</li>
<li>and/or provide a <code>Timeout::wake</code> that raises an timeout error only if the block is currently sleeping.</li>
</ul>
<p>Details:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">q</span> <span class="o">=</span> <span class="no">Queue</span><span class="p">.</span><span class="nf">new</span>
<span class="c1"># ...</span>
<span class="n">elem</span> <span class="o">=</span> <span class="no">Timeout</span><span class="o">::</span><span class="n">timeout</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span> <span class="p">{</span> <span class="n">q</span><span class="p">.</span><span class="nf">pop</span> <span class="p">}</span> <span class="c1"># => It is possible that an element is retreived from the queue but never stored in `elem`</span>
<span class="n">elem</span> <span class="o">=</span> <span class="no">Timeout</span><span class="o">::</span><span class="n">wake</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span> <span class="p">{</span> <span class="n">q</span><span class="p">.</span><span class="nf">pop</span> <span class="p">}</span> <span class="c1"># => Guaranteed that either element is retrieved from the queue or an exception is raised, never both</span>
<span class="no">Timeout</span><span class="o">::</span><span class="n">wake</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span> <span class="p">{</span> <span class="kp">loop</span> <span class="p">{}</span> <span class="p">}</span> <span class="c1"># => infinite loop</span>
<span class="c1"># and/or</span>
<span class="n">elem</span> <span class="o">=</span> <span class="n">q</span><span class="p">.</span><span class="nf">pop</span><span class="p">(</span><span class="ss">timeout: </span><span class="mi">42</span><span class="p">)</span>
</code></pre>
<p>Currently, the only reliable way to have a Queue that accepts a timeout is to re-implement it from scratch. This post describe how involved that can be: <a href="https://spin.atomicobject.com/2017/06/28/queue-pop-with-timeout-fixed/" class="external">https://spin.atomicobject.com/2017/06/28/queue-pop-with-timeout-fixed/</a></p> Ruby master - Bug #17359 (Open): Ractor copy mode is not Ractor-safehttps://bugs.ruby-lang.org/issues/173592020-12-01T08:29:37Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>It should not be possible to mutate an object across Ractors, but the copy mode allows it currently:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Foo</span>
<span class="nb">attr_accessor</span> <span class="ss">:x</span>
<span class="k">def</span> <span class="nf">initialize_copy</span><span class="p">(</span><span class="o">*</span><span class="p">)</span>
<span class="vg">$last</span> <span class="o">=</span> <span class="nb">self</span>
<span class="k">super</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">o</span> <span class="o">=</span> <span class="no">Foo</span><span class="p">.</span><span class="nf">new</span>
<span class="n">o</span><span class="p">.</span><span class="nf">x</span> <span class="o">=</span> <span class="mi">42</span>
<span class="n">r</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">o</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">copy</span><span class="o">|</span>
<span class="nb">puts</span> <span class="n">copy</span><span class="p">.</span><span class="nf">x</span> <span class="c1"># => 42</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">yield</span> <span class="ss">:sync</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">yield</span> <span class="ss">:sync</span>
<span class="nb">puts</span> <span class="n">copy</span><span class="p">.</span><span class="nf">x</span> <span class="c1"># => 666</span>
<span class="k">end</span>
<span class="n">r</span><span class="p">.</span><span class="nf">take</span> <span class="c1"># => :sync</span>
<span class="vg">$last</span><span class="p">.</span><span class="nf">x</span> <span class="o">=</span> <span class="mi">666</span>
<span class="n">r</span><span class="p">.</span><span class="nf">take</span> <span class="c1"># => :sync</span>
<span class="n">r</span><span class="p">.</span><span class="nf">take</span>
</code></pre>
<p>Maybe the <code>copy</code> object should be marked as moved?</p> Ruby master - Feature #17357 (Open): `Queue#pop` should have a block form for closed queueshttps://bugs.ruby-lang.org/issues/173572020-12-01T01:42:16Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>It is currently difficult to reliably distinguish a <code>nil</code> value in a queue from the <code>nil</code> that is returned when a Queue is closed:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">n</span> <span class="o">=</span> <span class="mi">100_000</span>
<span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">t2</span> <span class="o">=</span> <span class="no">Thread</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="n">n</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="no">Thread</span><span class="p">.</span><span class="nf">pass</span> <span class="p">}}</span> <span class="c1"># to make things less predictable</span>
<span class="n">n</span><span class="p">.</span><span class="nf">times</span><span class="p">.</span><span class="nf">count</span> <span class="k">do</span>
<span class="n">q</span> <span class="o">=</span> <span class="no">Queue</span><span class="p">.</span><span class="nf">new</span>
<span class="n">t</span> <span class="o">=</span> <span class="no">Thread</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="n">q</span><span class="p">.</span><span class="nf">pop</span><span class="p">;</span> <span class="n">result</span> <span class="o"><<</span> <span class="n">q</span><span class="p">.</span><span class="nf">closed?</span> <span class="p">}</span>
<span class="n">q</span> <span class="o"><<</span> <span class="kp">nil</span>
<span class="n">q</span><span class="p">.</span><span class="nf">close</span>
<span class="n">t</span><span class="p">.</span><span class="nf">join</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">result</span><span class="p">.</span><span class="nf">count</span><span class="p">(</span><span class="kp">true</span><span class="p">)</span> <span class="c1"># => some number usually > 9990 and < 10000</span>
</code></pre>
<p>To be completely sure, one needs a Mutex or wrap/unwrap <code>nil</code> values.</p>
<p><code>Queue#pop</code> should offer a surefire way to handle closed queues. I propose that an optional block be called in this case:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">q</span> <span class="o">=</span> <span class="no">Queue</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">close</span>
<span class="n">q</span><span class="p">.</span><span class="nf">pop</span> <span class="c1"># => nil</span>
<span class="n">q</span><span class="p">.</span><span class="nf">pop</span> <span class="p">{</span> <span class="ss">:closed</span> <span class="p">}</span> <span class="c1"># => :closed</span>
</code></pre>
<p>Proposed PR: <a href="https://github.com/ruby/ruby/pull/3830" class="external">https://github.com/ruby/ruby/pull/3830</a></p> Ruby master - Bug #17344 (Closed): `Ractor#shareable?` confused by recursive structureshttps://bugs.ruby-lang.org/issues/173442020-11-26T09:46:49Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">y</span> <span class="o">=</span> <span class="p">[];</span> <span class="n">x</span> <span class="o">=</span> <span class="p">[</span><span class="n">y</span><span class="p">,</span> <span class="p">{}].</span><span class="nf">freeze</span><span class="p">;</span> <span class="n">y</span> <span class="o"><<</span> <span class="n">x</span><span class="p">;</span> <span class="n">y</span><span class="p">.</span><span class="nf">freeze</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">shareable?</span><span class="p">(</span><span class="n">y</span><span class="p">)</span> <span class="c1"># => false, ok, the `{}` is not frozen</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">shareable?</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="c1"># => false, ok</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">shareable?</span><span class="p">(</span><span class="n">y</span><span class="p">)</span> <span class="c1"># => true, not ok!</span>
</code></pre>
<p>The error is that we can not mark anything as shareable until the whole tree has been searched successfully. Only when the full traversal is successful, then all visited objects can be marked as shareable. There might be a more clever way, but I couldn't think of one when working on my backport.</p> Ruby master - Bug #17343 (Closed): Ractor can't clone frozen structureshttps://bugs.ruby-lang.org/issues/173432020-11-26T09:44:49Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span><span class="p">([[]].</span><span class="nf">freeze</span><span class="p">)</span> <span class="p">{}</span> <span class="c1"># => FrozenError</span>
</code></pre>
<p>See <a href="https://github.com/ruby/ruby/pull/3817" class="external">https://github.com/ruby/ruby/pull/3817</a></p> Ruby master - Bug #17310 (Closed): Closed ractors should diehttps://bugs.ruby-lang.org/issues/173102020-11-08T03:17:25Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>While backporting Ractors, I found this issue:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="mi">10</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="nb">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span>
<span class="nb">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="nb">puts</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">count</span> <span class="c1"># => 1, ok</span>
<span class="c1"># but:</span>
<span class="mi">10</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="nb">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span> <span class="p">}.</span><span class="nf">close</span> <span class="p">}</span>
<span class="nb">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">count</span> <span class="c1"># => 11, should be 1</span>
</code></pre> Ruby master - Feature #17286 (Open): `Ractor.new` should accept `move: true`https://bugs.ruby-lang.org/issues/172862020-10-26T05:10:00Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Another surprise when writing my backport is that <code>Ractor.new</code> does not accept <code>move:</code> keyword argument.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">val</span><span class="p">,</span> <span class="ss">move: </span><span class="kp">true</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">data</span><span class="o">|</span> <span class="o">...</span> <span class="p">}</span>
<span class="c1"># equivalent to</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="n">data</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">receive</span><span class="p">;</span> <span class="o">...</span> <span class="p">}.</span><span class="nf">tap</span> <span class="p">{</span> <span class="o">|</span><span class="n">r</span><span class="o">|</span> <span class="n">r</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="n">val</span><span class="p">,</span> <span class="ss">move: </span><span class="kp">true</span><span class="p">)</span> <span class="p">}</span>
</code></pre> Ruby master - Feature #17285 (Open): Less strict `Ractor.select`https://bugs.ruby-lang.org/issues/172852020-10-26T02:49:44Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Summary: could we have a way for <code>Ractor.select</code> to skip ractors with closed queues and raise only if no ractor with an open queue remains?</p>
<p>Detail:</p>
<p>I <a href="https://github.com/marcandre/backports/pull/153" class="external">backported <code>Ractor</code> for earlier Ruby versions</a>, as I'd like to use it in some gems that would work great in 3.0 and work ok in older Rubies without rewriting. That was a lot of fun :-)</p>
<p>One surprise for me was that <code>Ractor.select</code> enforces that no given ractor is terminated(*).</p>
<p>This means that one must remove terminated ractors from a pool of ractors before calling <code>select</code> again:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">pool</span> <span class="o">=</span> <span class="mi">20</span><span class="p">.</span><span class="nf">times</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span><span class="p">{</span> <span class="n">do_processing</span> <span class="p">}</span> <span class="p">}</span>
<span class="mi">20</span><span class="p">.</span><span class="nf">times</span> <span class="k">do</span>
<span class="n">ractor</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">select</span><span class="p">(</span><span class="o">*</span><span class="n">pool</span><span class="p">)</span>
<span class="n">handle</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="n">pool</span><span class="p">.</span><span class="nf">delete</span><span class="p">(</span><span class="n">ractor</span><span class="p">)</span> <span class="c1"># necessary!</span>
<span class="k">end</span>
</code></pre>
<ol start="0">
<li>
<p>This can be tedious, but I know I'm very lazy</p>
</li>
<li>
<p>It is not convenient to share a pool between different ractors. Try writing code that starts 5 ractors that would consume the results from <code>pool</code> above.</p>
</li>
<li>
<p>It might require special synchronization if the ractors may yield a variable number of values:</p>
</li>
</ol>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">do_processing</span>
<span class="nb">rand</span><span class="p">(</span><span class="mi">10</span><span class="p">).</span><span class="nf">times</span> <span class="k">do</span> <span class="p">{</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">yield</span> <span class="ss">:a_result</span>
<span class="p">}</span>
<span class="ss">:finish</span>
<span class="k">end</span>
<span class="n">pool</span> <span class="o">=</span> <span class="mi">20</span><span class="p">.</span><span class="nf">times</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span><span class="p">{</span> <span class="n">do_processing</span> <span class="p">}</span> <span class="p">}</span>
<span class="k">until</span> <span class="n">pool</span><span class="p">.</span><span class="nf">empty?</span> <span class="k">do</span>
<span class="n">ractor</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">select</span><span class="p">(</span><span class="o">*</span><span class="n">pool</span><span class="p">)</span>
<span class="k">if</span> <span class="n">result</span> <span class="o">==</span> <span class="ss">:finish</span>
<span class="n">pool</span><span class="p">.</span><span class="nf">delete</span><span class="p">(</span><span class="n">ractor</span><span class="p">)</span>
<span class="k">else</span>
<span class="n">do_something_with</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>I would like to propose that it would be allowed (by default or at least via keyword parameter) to call <code>select</code> on terminated ractors, as long as there is at least one remaining open one.</p>
<p>This would make it very to resolve 1 and 2 above. Here's an example combine them both together:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">do_processing</span>
<span class="nb">rand</span><span class="p">(</span><span class="mi">10</span><span class="p">).</span><span class="nf">times</span> <span class="k">do</span> <span class="p">{</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">yield</span> <span class="ss">:a_result</span>
<span class="p">}</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">current</span><span class="p">.</span><span class="nf">close</span> <span class="c1"># avoid yielding a value at the end</span>
<span class="k">end</span>
<span class="n">pool</span> <span class="o">=</span> <span class="mi">20</span><span class="p">.</span><span class="nf">times</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span><span class="p">{</span> <span class="n">do_processing</span> <span class="p">}</span> <span class="p">}.</span><span class="nf">freeze</span>
<span class="mi">5</span><span class="p">.</span><span class="nf">times</span> <span class="k">do</span> <span class="c1"># divide processing into 5 ractors</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">pool</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">pool</span><span class="o">|</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="n">_ractor</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">select</span><span class="p">(</span><span class="o">*</span><span class="n">pool</span><span class="p">)</span> <span class="c1"># with my proposed lax select</span>
<span class="n">do_something_with</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>The <code>loop</code> above terminates when <code>Ractor.select</code> raises an error once the whole <code>pool</code> is terminated.</p>
<p>I'm new to actors but my intuition currently is that I will never want to take care of a pool of Ractors myself and would always prefer if <code>Ractor.select</code> did it for me. Are there use-cases where <code>Ractor.select</code> raising an error if it encounters a closed queue is helpful?</p>
<p>Notes:</p>
<ul>
<li>(*) <code>Ractor.select</code> doesn't really enforce ractors to be opened of course, it will work if the ractors are consumed in the right order, like in this example by chance:</li>
</ul>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="mi">10</span><span class="p">.</span><span class="nf">times</span><span class="p">.</span><span class="nf">map</span> <span class="k">do</span>
<span class="n">r</span> <span class="o">=</span> <span class="mi">2</span><span class="p">.</span><span class="nf">times</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span><span class="p">{</span> <span class="nb">sleep</span><span class="p">(</span><span class="mf">0.05</span><span class="p">);</span> <span class="ss">:ok</span> <span class="p">}</span> <span class="p">}</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">select</span><span class="p">(</span><span class="o">*</span><span class="n">r</span><span class="p">)</span> <span class="c1"># Get first available result</span>
<span class="c1"># Don't remove the ractor from `r`</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">select</span><span class="p">(</span><span class="o">*</span><span class="n">r</span><span class="p">).</span><span class="nf">last</span> <span class="k">rescue</span> <span class="ss">:error</span> <span class="c1"># Get second result</span>
<span class="k">end</span>
<span class="c1"># => [:ok, :error, :error, :error, :error, :error, :error, :ok, :ok, :ok]</span>
</code></pre>
<ul>
<li>I think <code>Ractor.select(*pool, yield_value: 42)</code> would raise only if the current outgoing queue is closed, even if the whole pool was terminated</li>
<li>Similarly <code>Ractor.select(*pool, Ractor.current)</code> would raise only if the current incomming queue is also closed.</li>
</ul> Ruby master - Feature #17210 (Open): More readable and useful `Set#inspect`https://bugs.ruby-lang.org/issues/172102020-10-02T04:33:18Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I would like to change <code>Set#inspect</code>/<code>to_s</code>:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># before</span>
<span class="nb">puts</span> <span class="no">Set</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="c1"># => "#<Set: {1, 2, 3}>"</span>
<span class="c1"># after</span>
<span class="nb">puts</span> <span class="no">Set</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="c1"># => "Set[1, 2, 3]"</span>
</code></pre>
<p>This output is shorter, readable, and has the property that it corresponds to Ruby code</p> Ruby master - Bug #17124 (Closed): Wrong "ambiguous first argument" warninghttps://bugs.ruby-lang.org/issues/171242020-08-18T20:02:54Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<pre><code class="sh syntaxhl" data-language="sh"><span class="nv">$ </span>ruby <span class="nt">-v</span> <span class="nt">-e</span> <span class="s2">"x='a'; x.match? /[a-z]/"</span>
ruby 2.8.0dev <span class="o">(</span>2020-07-30T14:07:06Z master 352895b751<span class="o">)</span> <span class="o">[</span>x86_64-darwin18]
<span class="nt">-e</span>:1: warning: ambiguous first argument<span class="p">;</span> put parentheses or a space even after <span class="sb">`</span>/<span class="s1">' operator
</span></code></pre>
<p>There is no <code>/</code> operator in there and there is also no ambiguity as adding a space after the first <code>/</code> is a syntax error.</p>
<p>Is it possible to remove the warning altogether when the argument is lexed as a regexp?</p>
<p>The message could use a rewording too, maybe "ambiguous first argument; put parentheses around argument or add a space after `/' operator"</p> Ruby master - Bug #17092 (Closed): Array#flatten with finite depth should flatten recursive arrayshttps://bugs.ruby-lang.org/issues/170922020-07-30T12:29:24Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Recursive arrays can not be flattened currently:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">a</span> <span class="o">=</span> <span class="p">[];</span> <span class="n">a</span> <span class="o"><<</span> <span class="n">a</span>
<span class="n">a</span><span class="p">.</span><span class="nf">flatten</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="c1"># => tried to flatten recursive array</span>
</code></pre>
<p>The only valid reason to raise an error for recursive arrays is for flatten with no argument (or negative argument); the case for finite-depth flatten is not problematic.</p>
<p>This fix has the bonus of speeding up the finite-depth case in general.</p>
<p>I will merge <a href="https://github.com/ruby/ruby/pull/3374" class="external">https://github.com/ruby/ruby/pull/3374</a>, <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/4">@nobu (Nobuyoshi Nakada)</a> asked for backports.</p> Ruby master - Bug #17031 (Closed): `Kernel#caller_locations(m, n)` should be optimizedhttps://bugs.ruby-lang.org/issues/170312020-07-14T19:07:29Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p><code>Kernel#caller_locations(1, 1)</code> currently appears to needlessly allocate memory for the whole backtrace.</p>
<p>It allocates ~20kB for a 800-deep stacktrace, vs 1.6 kB for a shallow backtrace.<br>
It is also much slower for long stacktraces: about 7x slower for a 800-deep backtrace than for a shallow one.</p>
<p>Test used:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">do_something</span>
<span class="n">location</span> <span class="o">=</span> <span class="n">caller_locations</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">).</span><span class="nf">first</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">test</span><span class="p">(</span><span class="n">depth</span><span class="p">,</span> <span class="n">trigger</span><span class="p">)</span>
<span class="n">do_something</span> <span class="k">if</span> <span class="n">depth</span> <span class="o">==</span> <span class="n">trigger</span>
<span class="nb">test</span><span class="p">(</span><span class="n">depth</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">trigger</span><span class="p">)</span> <span class="k">unless</span> <span class="n">depth</span> <span class="o">==</span> <span class="mi">0</span>
<span class="k">end</span>
<span class="nb">require</span> <span class="s1">'benchmark/ips'</span>
<span class="no">Benchmark</span><span class="p">.</span><span class="nf">ips</span> <span class="k">do</span> <span class="o">|</span><span class="n">x</span><span class="o">|</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="p">(</span><span class="ss">:short_backtrace</span> <span class="p">)</span> <span class="p">{</span><span class="nb">test</span><span class="p">(</span><span class="mi">800</span><span class="p">,</span><span class="mi">800</span><span class="p">)}</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="p">(</span><span class="ss">:long_backtrace</span> <span class="p">)</span> <span class="p">{</span><span class="nb">test</span><span class="p">(</span><span class="mi">800</span><span class="p">,</span> <span class="mi">0</span><span class="p">)}</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span> <span class="p">(</span><span class="ss">:no_caller_locations</span><span class="p">)</span> <span class="p">{</span><span class="nb">test</span><span class="p">(</span><span class="mi">800</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)}</span>
<span class="k">end</span>
<span class="nb">require</span> <span class="s1">'memory_profiler'</span>
<span class="no">MemoryProfiler</span><span class="p">.</span><span class="nf">report</span> <span class="p">{</span> <span class="nb">test</span><span class="p">(</span><span class="mi">800</span><span class="p">,</span><span class="mi">800</span><span class="p">)</span> <span class="p">}.</span><span class="nf">pretty_print</span><span class="p">(</span><span class="ss">scale_bytes: </span><span class="kp">true</span><span class="p">,</span> <span class="ss">detailed_report: </span><span class="kp">false</span><span class="p">)</span>
<span class="no">MemoryProfiler</span><span class="p">.</span><span class="nf">report</span> <span class="p">{</span> <span class="nb">test</span><span class="p">(</span><span class="mi">800</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="p">}.</span><span class="nf">pretty_print</span><span class="p">(</span><span class="ss">scale_bytes: </span><span class="kp">true</span><span class="p">,</span> <span class="ss">detailed_report: </span><span class="kp">false</span><span class="p">)</span>
</code></pre>
<p>Found when checking memory usage on RuboCop.</p> Ruby master - Bug #17030 (Closed): Enumerable#grep{_v} should be optimized for Regexphttps://bugs.ruby-lang.org/issues/170302020-07-13T20:26:50Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Currently,</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">array</span><span class="p">.</span><span class="nf">select</span> <span class="p">{</span> <span class="o">|</span><span class="n">e</span><span class="o">|</span> <span class="n">e</span><span class="p">.</span><span class="nf">match?</span><span class="p">(</span><span class="no">REGEXP</span><span class="p">)</span> <span class="p">}</span>
</code></pre>
<p>is about three times faster and six times more memory efficient than</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">array</span><span class="p">.</span><span class="nf">grep</span><span class="p">(</span><span class="no">REGEXP</span><span class="p">)</span>
</code></pre>
<p>This is because <code>grep</code> calls <code>Regexp#===</code>, which creates useless <code>MatchData</code>.</p> Ruby master - Bug #16996 (Closed): Hash should avoid doing unnecessary rehashhttps://bugs.ruby-lang.org/issues/169962020-06-27T08:20:32Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Pop quiz: Which is the fastest way to get a copy of a Hash <code>h</code>?</p>
<p>If, like me, you thought <code>h.dup</code> (of course, right?), you are actually wrong.</p>
<p>The fastest way is to call <code>h.merge</code>. Try it:</p>
<pre><code>require 'benchmark/ips'
lengths = 1..50
h = lengths.to_h { |i| ['x' * i, nil] }
Benchmark.ips do |x|
x.report("dup") { h.dup }
x.report("merge") { h.merge }
end
</code></pre>
<p>I get</p>
<pre><code>Calculating -------------------------------------
dup 259.233k (± 9.2%) i/s - 1.285M in 5.013445s
merge 944.095k (± 8.2%) i/s - 4.693M in 5.005315s
</code></pre>
<p>Yup, it's <em>3.5x faster</em> with this example!!</p>
<p>Why? Because <code>Hash#dup</code> does a rehash, and <code>merge</code> does not.</p>
<p>Pop quiz 2: which methods of <code>Hash</code> that produce a new hash do a rehash?</p>
<p>Answer: it depends on the method and on the Ruby version</p>
<pre><code>
+---------------------------------+------+-----+-----+-----+-----+-----+-----+-----+-----+
| Does this rehash? | head | 2.7 | 2.6 | 2.5 | 2.4 | 2.3 | 2.2 | 2.1 | 2.0 |
+---------------------------------+------+-----+-----+-----+-----+-----+-----+-----+-----+
| h.dup / clone | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
+---------------------------------+------+-----+-----+-----+-----+-----+-----+-----+-----+
| h.select{true} / reject{false} | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
+---------------------------------+------+-----+-----+-----+-----+-----+-----+-----+-----+
| h.select!{true} / reject!{false}| Ø | Ø | Ø | Ø | Ø | Ø | Ø | Ø | Ø |
+---------------------------------+------+-----+-----+-----+-----+-----+-----+-----+-----+
| sub_h.to_h | Ø | Ø | Ø | Ø | Ø | Ø | Ø | Ø | Ø |
+---------------------------------+------+-----+-----+-----+-----+-----+-----+-----+-----+
| h.merge({}) | Ø | Ø | Ø | Ø | Yes | Yes | Yes | Yes | Yes |
+---------------------------------+------+-----+-----+-----+-----+-----+-----+-----+-----+
| h.merge | Ø | Ø | Ø | n/a |
+---------------------------------+------+-----+-----+-----+-----+-----+-----+-----+-----+
| h.transform_values(&:itself) | Ø | Ø | Yes | Yes | Yes | n/a |
+---------------------------------+------+-----+-----+-----+-----+-----+-----+-----+-----+
(where `sub_h = Class.new(Hash).replace(h)`, Ø = no rehash)
</code></pre>
<p>So in Ruby head, doing <code>h.merge({})</code> or even <code>h.transform_values(&:itself)</code> will be much faster than <code>h.dup</code> (but slower in Ruby 2.4) (*)</p>
<p>Notice that <code>select</code> rehashes, but <code>select!</code> doesn't, so the fastest way to do a <code>select</code> in Ruby is... not to call select and instead to actually do a <code>merge.select!</code>! (*)</p>
<p>*: on hashes with non-negligible hash functions</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Hash</span>
<span class="k">def</span> <span class="nf">fast_select</span><span class="p">(</span><span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="n">merge</span><span class="p">.</span><span class="nf">select!</span><span class="p">(</span><span class="o">&</span><span class="n">block</span><span class="p">)</span> <span class="c1"># don't call dup because it's slow</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="no">Benchmark</span><span class="p">.</span><span class="nf">ips</span> <span class="k">do</span> <span class="o">|</span><span class="n">x</span><span class="o">|</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span><span class="p">(</span><span class="s2">"select"</span><span class="p">)</span> <span class="p">{</span> <span class="n">h</span><span class="p">.</span><span class="nf">select</span><span class="p">{</span><span class="kp">true</span><span class="p">}</span> <span class="p">}</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span><span class="p">(</span><span class="s2">"fast_select"</span><span class="p">)</span> <span class="p">{</span> <span class="n">h</span><span class="p">.</span><span class="nf">fast_select</span><span class="p">{</span><span class="kp">true</span><span class="p">}</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre>
<p>On my test case above, <code>fast_select</code> is <em>2.5x faster</em> than <code>select</code>. <code>fast_select</code> will always return exactly the same result (unless the receiver needed a rehash).</p>
<p>Pop quiz 3: Is this a bug or a feature?</p>
<p>It should be clear that no feature of Ruby should be re-implementable in Ruby with a 3.5x / 2.5x speed gain, so many would think "of course it's a bug".</p>
<p>Well, <a href="https://bugs.ruby-lang.org/issues/16121" class="external">https://bugs.ruby-lang.org/issues/16121</a> seems to think that <code>Hash#dup</code>'s rehash is a feature...<br>
Why?<br>
Because there is actually a test that <code>dup</code> does a rehash<br>
Why?<br>
Because a test of <code>Set</code> was failing otherwise!<br>
Commit: <a href="https://github.com/ruby/ruby/commit/a34a3c2caae4c1fbd" class="external">https://github.com/ruby/ruby/commit/a34a3c2caae4c1fbd</a><br>
Short discussion: <a href="http://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/ruby/ruby-core/48040?47945-48527" class="external">http://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/ruby/ruby-core/48040?47945-48527</a><br>
Actual test: <a href="https://github.com/ruby/ruby/blob/master/test/test_set.rb#L621-L625" class="external">https://github.com/ruby/ruby/blob/master/test/test_set.rb#L621-L625</a><br>
Why?<br>
This test construct a <code>Set</code> that needs to be rehashed (by mutating an element of the set after it is added), and then checks that <code>rehash_me == rehash_me.clone</code>.<br>
That test is bogus. It passes for obscure and undocumented reasons, and <code>rehash_me.clone == rehash_me</code> doesn't pass.<br>
Today, it is official that sets with elements that are later mutated must be <code>Set#reset</code>, so it is official that this should not be relied upon.</p>
<p>Probably more clear is the case of <code>select/reject</code> (but I didn't check for failing test), and even more clear that <code>merge</code> changed in Ruby 2.5 and <code>transform_values</code> in 2.7, but not a single <code>NEWS</code> file mentions the word "rehash".</p>
<p>My conclusion is that Hash should avoid doing an unnecessary rehash: <code>dup</code>/<code>clone</code>/<code>select</code>/<code>reject</code>. We probably should add a reminder in the <code>NEWS</code> that if anyone mutates a key of a Hash, or an element of a Set and does not call <code>rehash</code>/<code>reset</code>, improper behavior should be expected.</p>
<p>Let's make <code>Hash#dup/clone/select/reject</code> fast please.</p>
<p>Any objection?</p> Ruby master - Feature #16993 (Open): Sets: from hash keys using Hash#key_sethttps://bugs.ruby-lang.org/issues/169932020-06-26T20:31:27Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>To create a set from hash keys currently implies a temporary array for all keys, rehashing all those keys and rebuilding a hash. Instead, the hash could be copied and its values set to <code>true</code>.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">h</span> <span class="o">=</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span>
<span class="c1"># Now:</span>
<span class="no">Set</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">h</span><span class="p">.</span><span class="nf">keys</span><span class="p">)</span> <span class="c1"># => Set[:a]</span>
<span class="c1"># After</span>
<span class="n">h</span><span class="p">.</span><span class="nf">key_set</span> <span class="c1"># => Set[:a], efficiently.</span>
</code></pre> Ruby master - Feature #16992 (Open): Sets: officially orderedhttps://bugs.ruby-lang.org/issues/169922020-06-26T20:30:50Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Officially, set elements have uncertain order. This predades when Hash started being ordered (Ruby 1.9.0, Xmas 2007). Sets have since been de-facto insertion-ordered. FYI, in those 13 years, there have been about 70 commits to <code>lib/set.rb</code>.</p>
<p>I have the impression that a non-negligible amount of code in the wild rely on sets being ordered, at least under most circumstances. I feel that this should be officialized.</p>
<p>If sets are truly unordered, then why do we hesitate to make an optimization of <code>&</code> and <code>|</code>: <a href="https://bugs.ruby-lang.org/issues/15281" class="external">https://bugs.ruby-lang.org/issues/15281</a></p>
<p>See also: <a href="https://bugs.ruby-lang.org/issues/14069" class="external">https://bugs.ruby-lang.org/issues/14069</a></p> Ruby master - Feature #16990 (Open): Sets: operators compatibility with Arrayhttps://bugs.ruby-lang.org/issues/169902020-06-26T20:27:17Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>We currently have <code>set <operator> array</code> work fine:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Set</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="c1"># => Set[1, 2]</span>
</code></pre>
<p>Nothing works in the reverse order:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="no">Set</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="c1"># => no implicit conversion of Set into Array</span>
<span class="c1"># should be:</span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="no">Set</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="c1"># => [1, 2]</span>
</code></pre>
<a name="set-like-operators"></a>
<h4 >set-like operators<a href="#set-like-operators" class="wiki-anchor">¶</a></h4>
<p>Note that the situation is particularly frustrating for <code>&</code>, <code>|</code> and <code>-</code>.<br>
If someone wants to do <code>ary - set</code>, one <strong>has</strong> to do <code>ary - set.to_a</code> which will, internally, do a <code>to_set</code>, so what is happening is <code>set.to_a.to_set</code>!! (assuming <code>ary</code> is over <code>SMALL_ARRAY_LEN == 16</code> size, otherwise it's still doing in <code>O(ary * set)</code> instead of <code>O(ary)</code>).</p>
<p>The same holds with <code>&</code> and <code>|</code>; see order issue as to why this can <em>not</em> (officially) be done any other way.</p>
<p>Reminder:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">ary</span> <span class="o">&</span> <span class="n">ary</span><span class="p">.</span><span class="nf">reverse</span> <span class="c1"># => ary</span>
<span class="no">Set</span><span class="p">[</span><span class="o">*</span><span class="n">ary</span><span class="p">]</span> <span class="o">&</span> <span class="no">Set</span><span class="p">[</span><span class="o">*</span><span class="n">ary</span><span class="p">.</span><span class="nf">reverse</span><span class="p">]</span> <span class="c1"># => Set[*ary.reverse], officially order is indeterminate</span>
</code></pre> Ruby master - Feature #16989 (Open): Sets: need ♥️https://bugs.ruby-lang.org/issues/169892020-06-26T20:18:17Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I am opening a series of feature requests on <code>Set</code>, all of them based on this usecase.</p>
<p>The main usecase I have in mind is my recent experience with <code>RuboCop</code>. I noticed a big number of frozen arrays being used only to later call <code>include?</code> on them. This is <code>O(n)</code> instead of <code>O(1)</code>.</p>
<p>Trying to convert them to <code>Set</code>s causes major compatibility issues, as well as very frustrating situations and some cases that would make them much less efficient.</p>
<p>Because of these incompatibilities, <code>RuboCop</code> is in the process of using a custom class based on <code>Array</code> with optimized <code>include?</code> and <code>===</code>. <code>RuboCop</code> runs multiple checks on Ruby code. Those checks are called cops. <code>RuboCop</code> performance is (IMO) pretty bad and some cops currently are in <code>O(n^2)</code> where n is the size of the code being inspected. Even given these extremely inefficient cops, optimizing the 100+ such arrays (most of which are quite small btw) gave a 5% speed boost.</p>
<p>RuboCop PRs for reference: <a href="https://github.com/rubocop-hq/rubocop-ast/pull/29" class="external">https://github.com/rubocop-hq/rubocop-ast/pull/29</a><br>
<a href="https://github.com/rubocop-hq/rubocop/pull/8133" class="external">https://github.com/rubocop-hq/rubocop/pull/8133</a></p>
<p>My experience tells me that there are many other opportunities to use <code>Set</code>s that are missed because <code>Set</code>s are not builtin, not known enough and have no shorthand notation.</p>
<p>In this issue I'd like to concentrate the discussion on the following request: <code>Set</code>s should be core objects, in the same way that <code>Complex</code> were not and are now. Some of the upcoming feature requests would be easier (or only possible) to implement were <code>Set</code>s builtin.</p> Ruby master - Feature #16985 (Open): Improve `pp` for `Hash` and `String`https://bugs.ruby-lang.org/issues/169852020-06-25T17:28:06Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Could we improve <code>pp</code> for <code>Hash</code> and <code>String</code>:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">pp</span><span class="p">({</span><span class="ss">hello: </span><span class="s1">'My name is "Marc-André"'</span><span class="p">})</span>
<span class="c1"># =></span>
<span class="p">{</span><span class="ss">hello: </span><span class="s1">'My name is "Marc-André"'</span><span class="p">}</span>
<span class="c1"># instead of</span>
<span class="p">{</span><span class="ss">:hello</span><span class="o">=></span><span class="s2">"My name is </span><span class="se">\"</span><span class="s2">Marc-André</span><span class="se">\"</span><span class="s2">"</span><span class="p">}</span>
</code></pre>
<p>If any key is non-symbol, they would continue to be output as <code><key> => <value></code>. If a string contains single quotes, or characters that need escaping (e.g. <code>"\n"</code>), current format would be used.</p>
<p>I'll gladly provide a PR if this is deemed acceptable.</p>
<p>I would even like this for <code>String#inspect</code> and <code>Hash#inspect</code> but it's not clear if this could lead to much incompatibility (maybe test suites?)</p> Ruby master - Bug #16281 (Closed): `irb -w` issues warninghttps://bugs.ruby-lang.org/issues/162812019-10-25T20:39:07Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<pre><code>$ irb -w
.rvm/rubies/ruby-head/lib/ruby/2.7.0/reline.rb:322: warning: instance variable @ambiguous_width not initialized
</code></pre>
<p>(Since f13db4adde532)</p> Ruby master - Feature #15918 (Open): Pattern matching for Sethttps://bugs.ruby-lang.org/issues/159182019-06-12T13:44:57Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Currently, <code>Set</code> does not respond to <code>deconstruct</code>. Shouldn't we implement it using <code>to_a</code>?</p>
<pre><code>require 'set'
case Set[1, 2, 3]
in [1, 2, 3]
p "match"
else
p "no match"
end
# => "no match", should be "match"
</code></pre> Ruby master - Feature #15881 (Open): Optimize deconstruct in pattern matchinghttps://bugs.ruby-lang.org/issues/158812019-05-27T16:19:35Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">A</span>
<span class="k">def</span> <span class="nf">deconstruct</span>
<span class="nb">puts</span> <span class="s1">'deconstruct called'</span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">case</span> <span class="no">A</span><span class="p">.</span><span class="nf">new</span>
<span class="k">in</span> <span class="p">[</span><span class="mi">2</span><span class="p">]</span>
<span class="mi">2</span>
<span class="k">in</span> <span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="mi">1</span>
<span class="k">else</span>
<span class="k">end</span>
</code></pre>
<p>Currently this outputs:</p>
<pre><code>deconstruct called
deconstruct called
=> 1
</code></pre>
<p>Shouldn't <code>deconstruct called</code> print only once, whenever the first deconstruction needed occurs?</p> Ruby master - Bug #15731 (Closed): Wrong evaluation of many keyword default arguments in 2.3 - 2.5https://bugs.ruby-lang.org/issues/157312019-03-27T17:53:16Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I don't know if it's worth fixing at this point, but we found a strange bug with evaluation of default keyword arguments when there are many of them (more than 32).</p>
<pre><code>def foo(
k0: puts(0), k1: puts(1), k2: puts(2), k3: puts(3), k4: puts(4),
k5: puts(5), k6: puts(6), k7: puts(7), k8: puts(8), k9: puts(9),
k10: puts(10), k11: puts(11), k12: puts(12), k13: puts(13), k14: puts(14),
k15: puts(15), k16: puts(16), k17: puts(17), k18: puts(18), k19: puts(19),
k20: puts(20), k21: puts(21), k22: puts(22), k23: puts(23), k24: puts(24),
k25: puts(25), k26: puts(26), k27: puts(27), k28: puts(28), k29: puts(29),
k30: puts(30), k31: puts(31), k32: puts(32), k33: puts(33)
)
k33
end
puts "No params:"
foo # Should print 1 to 33
puts "Only k33 param:"
foo(k33: 1) # Should print 1 to 32
puts "Only k32 and k33 params:"
r = foo(k32: 1, k33: 1) # Should print 1 to 31 and return 1
puts "Result: #{r.inspect}"
</code></pre>
<p>Ruby 2.4:<br>
last case is wrong. It prints 1 to 33 instead of 1 to 31 and returns <code>nil</code> instead of 1.</p>
<p>Ruby 2.5:<br>
same result for last case<br>
first two cases evaluates the default exactly the wrong parameters: those that are given and not for those not given. So it prints nothing and 33 respectively, instead of 1 to 33 and 1 to 32!</p>
<p>Ruby 2.6:<br>
results are ok</p>
<p>This strange behavior disappears with fewer keyword arguments.</p> Ruby master - Bug #15718 (Closed): YAML raises error when dumping strings with UTF32 encodinghttps://bugs.ruby-lang.org/issues/157182019-03-20T20:21:55Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<pre><code class="shell syntaxhl" data-language="shell">ruby <span class="nt">-r</span> yaml <span class="nt">-e</span> <span class="s2">"p YAML.dump( ''.force_encoding('UTF-32LE') )"</span>
Traceback <span class="o">(</span>most recent call last<span class="o">)</span>:
4: from <span class="nt">-e</span>:1:in <span class="sb">`</span><main><span class="s1">'
3: from /Users/work/.rvm/rubies/ruby-2.6.1/lib/ruby/2.6.0/psych.rb:513:in `dump'</span>
2: from /Users/work/.rvm/rubies/ruby-2.6.1/lib/ruby/2.6.0/psych/visitors/yaml_tree.rb:118:in <span class="sb">`</span>push<span class="s1">'
1: from /Users/work/.rvm/rubies/ruby-2.6.1/lib/ruby/2.6.0/psych/visitors/yaml_tree.rb:136:in `accept'</span>
/Users/work/.rvm/rubies/ruby-2.6.1/lib/ruby/2.6.0/psych/visitors/yaml_tree.rb:298:in <span class="sb">`</span>visit_String<span class="s1">': incompatible encoding regexp match (US-ASCII regexp with UTF-32LE string) (Encoding::CompatibilityError)
</span></code></pre>
<p>Surprisingly, this works in Ruby 2.4.x, but not in 2.2, 2.3, 2.5 nor 2.6!</p> Ruby master - Bug #15613 (Closed): Enumerator::Chain#each doesn't relay block signaturehttps://bugs.ruby-lang.org/issues/156132019-02-20T04:29:41Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Currently, the block given when iterating on the components of a <code>Enumerator::Chain</code> always have arity of -1 and <code>lambda?</code> is always <code>false</code>:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Foo</span>
<span class="kp">include</span> <span class="no">Enumerable</span>
<span class="k">def</span> <span class="nf">each</span><span class="p">(</span><span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="k">return</span> <span class="n">to_enum</span> <span class="k">unless</span> <span class="n">block</span>
<span class="nb">p</span> <span class="n">block</span><span class="p">.</span><span class="nf">arity</span><span class="p">,</span> <span class="n">block</span><span class="p">.</span><span class="nf">lambda?</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">foo</span> <span class="o">=</span> <span class="no">Foo</span><span class="p">.</span><span class="nf">new</span>
<span class="n">foo</span><span class="p">.</span><span class="nf">each</span><span class="p">(</span><span class="o">&-></span><span class="p">{})</span> <span class="c1"># => 0, true</span>
<span class="n">foo</span><span class="p">.</span><span class="nf">each</span><span class="p">.</span><span class="nf">each</span><span class="p">(</span><span class="o">&-></span><span class="p">{})</span> <span class="c1"># => 0, true</span>
<span class="n">foo</span><span class="p">.</span><span class="nf">chain</span><span class="p">.</span><span class="nf">each</span><span class="p">(</span><span class="o">&-></span><span class="p">{})</span> <span class="c1"># => -1, false. Would ideally be 0, true</span>
</code></pre>
<p>It would be preferable if the block information wasn't lost.</p> Ruby master - Bug #15376 (Closed): Default gems: how will it work exactly?https://bugs.ruby-lang.org/issues/153762018-12-04T06:38:35Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Some standard libraries will be gemified in Ruby 2.6+.</p>
<ol>
<li>
<p>I believe we need to provide documentation for this. The NEWS file doesn't have much at all. The respective READMEs also have no relevant information and are misleading (see <a href="https://github.com/ruby/ostruct/pull/6" class="external">https://github.com/ruby/ostruct/pull/6</a>). I'll be glad to write an initial draft if need be, with the answers to the following questions...</p>
</li>
<li>
<p>Can we play with this right now? Maybe publishing prelease versions of these libraries?</p>
</li>
<li>
<p>Has this been tested with <code>Gemfile</code> and <code>gemspec</code>, i.e. it will be possible to add a specification for one of these?</p>
</li>
<li>
<p>What is supposed to happen if one does <code>gem install ostruct</code> in Ruby 2.5 (with current bundler, and with future bundler)?</p>
</li>
<li>
<p>Will it be possible to use these in <code>Gemfile</code>s even with older Ruby (but recent <code>bundler</code>), so one could say <code>gem 'ostruct'</code> in a Gemfile and run <code>bundle install</code> in Ruby 2.5 without things exploding?</p>
</li>
<li>
<p>Depending on 4/5, shouldn't we specify a <code> required_rubygems_version</code> and/or <code> required_ruby_version</code> in the gemspecs?</p>
</li>
</ol> Ruby master - Bug #15332 (Closed): coverage and InstructionSequence regressionhttps://bugs.ruby-lang.org/issues/153322018-11-22T23:31:26Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>With the current head:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"> <span class="nb">require</span> <span class="s1">'coverage'</span>
<span class="no">Coverage</span><span class="p">.</span><span class="nf">start</span>
<span class="no">RubyVM</span><span class="o">::</span><span class="no">InstructionSequence</span><span class="p">.</span><span class="nf">compile</span><span class="p">(</span><span class="s2">"puts 'hi'"</span><span class="p">,</span> <span class="s1">'hello_world.rb'</span><span class="p">).</span><span class="nf">eval</span>
<span class="no">Coverage</span><span class="p">.</span><span class="nf">result</span> <span class="c1"># => {}, should be {'hello_world.rb' => [1]}</span>
</code></pre>
<p>This is not intended, right?</p> Ruby master - Feature #15330 (Open): autoload_relativehttps://bugs.ruby-lang.org/issues/153302018-11-21T23:43:41Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I'd like to propose a way to autoload a constant using a relative path.</p>
<p>It could look like:</p>
<pre><code>autoload_relative :MyConst, 'models/my_const'
</code></pre>
<p>My proposal raises two questions:</p>
<ol>
<li>what's the future of <code>autoload</code>?</li>
</ol>
<p>I believe that <code>autoload</code> has been there for years, it is used successfully and has no real alternative.</p>
<p>I looked at a sample of 430 top gems (took the 500 top ranked according to Libraries.io, removed those that I failed to process). The number of those gems that appear to use <code>autoload</code> at least once is 94 of those (22%).</p>
<p>The number of lines in the code where <code>autoload</code> is called can be quite big. The top 5 are:<br>
vagrant: 235<br>
yard: 206<br>
ffaker: 155<br>
aws-sdk: 152<br>
rdoc: 92</p>
<p>This is a minimum bound, as some gems might be using loops, my processing would only detect the one place in the code with <code>autoload</code>.</p>
<ol start="2">
<li>are many autoladed paths relative?</li>
</ol>
<p>My preliminary numbers indicate that of the 94 gems using autoload, at least 75 are autoloading some relative files. That's a lower bound, as my algorithm is pretty crude and will only count the simplest cases as being relative. An example of gem my algorithm does not detect is <code>yard</code>, because the author wrote a small method to map the relative paths to global paths (code here: <a href="https://github.com/lsegal/yard/blob/master/lib/yard/autoload.rb#L3" class="external">https://github.com/lsegal/yard/blob/master/lib/yard/autoload.rb#L3</a> )</p>
<p>Of those where my processing detects the relative requires, a vast majority are relative. The average is that 94% of autoloaded files are relative and would benefit from <code>require_relative</code></p>
<p>In summary: I am convinced that <code>autoload</code> should remain in Ruby indefinitely. <code>autoload_relative</code> would actually be more useful than <code>autoload</code>. Even if the future of <code>autoload</code> remains uncertain, I would recommend adding <code>autoload_relative</code>; if it is ever decided to actually remove <code>autoload</code>, removing <code>autoload_relative</code> would not really add to the (huge) burden of gem maintainers.</p> Ruby master - Feature #15277 (Open): at_exechttps://bugs.ruby-lang.org/issues/152772018-11-02T19:23:47Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>There's currently no easy way to have code executed before a subsequent call to <code>exec</code>. One has to monkey-patch the builtin method.</p>
<p>I'd like to propose a new method <code>at_exec</code> that would be very similar to <code>at_exit</code>, except that the callbacks are triggered before the current process is replaced by the external command.</p>
<pre><code># This would output "Hello", "Bye", and "Foo"
at_exec { puts "Bye!" }
puts "Hello"
exec "echo Foo"
</code></pre>
<p>Use case: we roll our own in <code>DeepCover</code>. Some test suites will call <code>exec</code>, and we need to store our counters before that happens.</p> Ruby master - Bug #15206 (Closed): require_relative in std_libhttps://bugs.ruby-lang.org/issues/152062018-10-06T03:17:08Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I noticed that there are some <code>require</code> for internal files in <code>lib/</code>. Ideally, they would be using <code>require_relative</code> instead. This is faster and more explicit.</p>
<p>Note: <code>require_relative</code> had a potential issue with symlinks that was fixed in 2.5, so libraries that might be loaded from earlier Ruby, like <code>rubygems</code>, can not yet be updated.</p>
<p>I've <a href="https://github.com/ruby/ruby/pull/1976" class="external">created a PR</a> and would like to know if there are comments / objections / things I'm missing.</p> Ruby master - Bug #15078 (Closed): Hash splat of empty hash should not create a positional argument.https://bugs.ruby-lang.org/issues/150782018-09-05T16:22:45Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Looks like <a class="issue tracker-1 status-5 priority-4 priority-default closed" title="Bug: Splat with empty keyword args gives unexpected results (Closed)" href="https://bugs.ruby-lang.org/issues/10856">#10856</a> is not completely fixed, but I can't reopen it</p>
<pre><code>def foo(*args); args; end
foo(**{}) # => []
foo(**Hash.new) # => [{}], should be []
</code></pre> Ruby master - Bug #14674 (Closed): New mismatched indentations warnings?https://bugs.ruby-lang.org/issues/146742018-04-10T17:43:28Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I recently got a failure in my test suite because ruby head warns of indentation it considers mismatched:</p>
<pre><code>$ ruby -w -e "
case
when :foo
end
"
-e:3: warning: mismatched indentations at 'when' with 'case' at 2
</code></pre>
<p>I hope this is not intentional and will be fixed.</p> Ruby master - Bug #14201 (Closed): Regression due to over optimization of hash splathttps://bugs.ruby-lang.org/issues/142012017-12-18T20:27:47Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>The following doesn't print anything nor raise anything in 2.5.0-rc1 or trunk:</p>
<pre><code>$ ruby -e "{**puts('hello')}; 42"
</code></pre>
<p>It should be the same as in Ruby 2.0-2.4:</p>
<pre><code>hello
-e:1:in `<main>': no implicit conversion of nil into Hash (TypeError)
</code></pre>
<p>Note: If you try to use the hash (e.g. passing as argument, storing in variable), then the correct behavior takes place. Found this bug through DeepCover's test suite.</p> Ruby master - Bug #14057 (Closed): TracePoint#enable and disable should not yield argumentshttps://bugs.ruby-lang.org/issues/140572017-10-26T06:54:53Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>While working on RubySpecs with Atul Bhosale, we discovered that <code>TracePoint#enable</code> and <code>#disable</code> yield <code>nil</code> instead of not yielding any argument.</p>
<p>This is mostly harmless as we usually use blocks, but it could create issues for lambdas/methods, for example:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">handle_trace</span><span class="p">;</span> <span class="k">end</span>
<span class="no">TracePoint</span><span class="p">.</span><span class="nf">new</span><span class="p">{}.</span><span class="nf">enable</span><span class="p">(</span><span class="o">&</span><span class="nb">method</span><span class="p">(</span><span class="ss">:handle_trace</span><span class="p">))</span> <span class="c1"># => ArgumentError: wrong number of arguments (given 1, expected 0)</span>
</code></pre>
<p>I'm fixing in trunk, would be nice to backport.</p> Ruby master - Bug #14031 (Closed): WeakRef example misleading and wronghttps://bugs.ruby-lang.org/issues/140312017-10-19T02:43:08Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I just noticed that the second part of the doc of <code>WeakRef</code> is misleading and later plainly wrong.</p>
<p>I'm talking about the example with <code>WeakHash</code> in<br>
<a href="https://ruby-doc.org/stdlib-2.4.0/libdoc/weakref/rdoc/WeakRef.html" class="external">https://ruby-doc.org/stdlib-2.4.0/libdoc/weakref/rdoc/WeakRef.html</a></p>
<p>The example shows</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">GC</span><span class="p">.</span><span class="nf">start</span>
<span class="n">c</span><span class="p">[</span><span class="s1">'foo'</span><span class="p">]</span> <span class="c1">#=> nil</span>
<span class="n">c</span><span class="p">[</span><span class="s1">'baz'</span><span class="p">]</span> <span class="c1">#=> nil</span>
<span class="n">c</span><span class="p">[</span><span class="s1">'qux'</span><span class="p">]</span> <span class="c1">#=> nil</span>
</code></pre>
<p>This is very <em>misleading</em>, since even before the GC that would be also the case, because <code>WeakHash</code> didn't redefine the lookup, and that <code>WeakHash.new('foo').eql?('foo')</code> is always <code>false</code>.</p>
<p>The doc goes on with:<br>
"You can see the local variable <code>omg</code> stayed, although its reference in our hash object was garbage collected, along with the rest of the keys and values."</p>
<p>That is <em>wrong</em>. The reference in our hash object was not garbage collected, since <code>omg</code> held on to it. This can be proven with <code>c.values.last # => 'lol'</code>.</p>
<p>My opinion is that fixing this example isn't worth it, and that even fixed it wouldn't add anything to the first simple example on WeakRef. In it's current form, it is worse dans not having it. Unless there are objections, I'll simply remove it.</p> Ruby master - Bug #14015 (Closed): Enumerable & Hash yielding arityhttps://bugs.ruby-lang.org/issues/140152017-10-14T20:19:18Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>The subtle difference between <code>yield 1, 2</code> and <code>yield [1, 2]</code> has always confused me.</p>
<p>Today I wanted to pass a method to Hash#flat_map and realized how it's even more confusing than I thought.</p>
<p>I assumed that <code>Hash#each</code> was calling <code>yield key, value</code>. But somehow it's not that simple:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}.</span><span class="nf">map</span><span class="p">(</span><span class="o">&-></span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">){})</span> <span class="c1"># => [nil]</span>
<span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}.</span><span class="nf">flat_map</span><span class="p">(</span><span class="o">&-></span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">){})</span> <span class="c1">#=> ArgumentError: wrong number of arguments (given 1, expected 2)</span>
</code></pre>
<p>What blows my mind, is that a custom method <code>each</code> that does <code>yield a, 1</code> has different result!</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="o"><<</span> <span class="n">o</span> <span class="o">=</span> <span class="no">Object</span><span class="p">.</span><span class="nf">new</span>
<span class="kp">include</span> <span class="no">Enumerable</span>
<span class="k">def</span> <span class="nf">each</span>
<span class="k">yield</span> <span class="ss">:a</span><span class="p">,</span> <span class="mi">1</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">o</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&-></span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">){})</span> <span class="c1"># => [nil]</span>
<span class="n">o</span><span class="p">.</span><span class="nf">flat_map</span><span class="p">(</span><span class="o">&-></span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">){})</span> <span class="c1"># => [nil] does not raise!!</span>
</code></pre>
<p>I don't even know how that's possible, since Hash doesn't have a specialized <code>flat_map</code> method...</p>
<p>Here's a list of methods that accept a lambda of arity 2 (as I would expect)<br>
For Hash<br>
each, any?, map, select, reject,<br>
For a custom yield<br>
each, any?, map, count, find_index, flat_map, all?, one?, none?, take_while, uniq</p>
<p>These two lists have <code>each</code>, <code>map</code> and <code>any?</code> in common. Others work in one flavor, not the other. Many require arity 1: find, sort_by, grep, grep_v, count, detect, find_index, find_all, ...</p>
<p>To make things even more impossible, <code>Hash#map</code> has been working with arity 2 since Ruby 2.4 only.</p>
<p>Finally, <code>Hash#each</code> changes the expected arity of <code>select</code>, <code>reject</code>, and <code>any?</code>, but not of <code>map</code>:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span> <span class="p">.</span><span class="nf">select</span><span class="p">(</span><span class="o">&-></span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">){})</span> <span class="c1"># => {}</span>
<span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}.</span><span class="nf">each</span><span class="p">.</span><span class="nf">select</span><span class="p">(</span><span class="o">&-></span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">){})</span> <span class="c1"># => wrong number of arguments (given 1, expected 2)</span>
</code></pre>
<p>Conclusion:</p>
<p>It seems more or less impossible to guess the expected arity of methods of Enumerable and of Hash, and they are not even consistent with one another. This makes these methods more or less unusable with lambdas.</p>
<p>While compatibility could be an issue, the fact that <code>Hash#map</code> has changed it's arity (I believe following <a href="https://bugs.ruby-lang.org/issues/13391" class="external">https://bugs.ruby-lang.org/issues/13391</a> ) makes me think that compatibility with the lesser used methods would be even less of a problem.</p>
<p>My personal wish: that the following methods be fixed to expect arity 2 for lambdas:</p>
<p>For both Hash & Enumerable:</p>
<ul>
<li>find, sort_by, grep, grep_v, detect, find_all, partition, group_by, min_by, max_by, minmax_by, reverse_each, drop_while, sum<br>
For Hash:</li>
<li>count, find_index, flat_map, all?, one?, none?, take_while, uniq<br>
For Enumerable:</li>
<li>select, reject</li>
</ul>
<p>Matz, what do you think?</p> Ruby master - Bug #14014 (Closed): NaN.finite?https://bugs.ruby-lang.org/issues/140142017-10-13T21:23:28Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Ruby gives contradictory answers for NaN:</p>
<pre><code>> (0/0.0).finite?
=> false
> Complex(0/0.0, 0).finite?
=> true
</code></pre>
<p>Note that <code>(0/0.0).infinite?</code> returns <code>nil</code>, so the float answer of <code>false</code> looks even more wrong.</p>
<p>The two solutions I see are either changing <code>Float#finite?</code> to return <code>true</code> for NaN, or to raise in both cases.</p>
<p>I'd lean towards raising in both cases, as NaN can not be said to be finite or infinite</p> Ruby master - Bug #13973 (Closed): super_method fails on some UnboundMethodshttps://bugs.ruby-lang.org/issues/139732017-10-05T02:01:33Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p><code>super_method</code> fails to go up the ancestry chain for methods that are only defined in included modules:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">module</span> <span class="nn">A</span>
<span class="k">def</span> <span class="nf">foo</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">module</span> <span class="nn">B</span>
<span class="k">def</span> <span class="nf">foo</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">C</span>
<span class="kp">include</span> <span class="no">A</span>
<span class="kp">include</span> <span class="no">B</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">D</span>
<span class="k">def</span> <span class="nf">foo</span>
<span class="k">end</span>
<span class="kp">include</span> <span class="no">A</span>
<span class="kp">include</span> <span class="no">B</span>
<span class="k">end</span>
<span class="no">C</span><span class="p">.</span><span class="nf">instance_method</span><span class="p">(</span><span class="ss">:foo</span><span class="p">)</span> <span class="c1"># => #<UnboundMethod: C(B)#foo> (ok)</span>
<span class="no">C</span><span class="p">.</span><span class="nf">instance_method</span><span class="p">(</span><span class="ss">:foo</span><span class="p">).</span><span class="nf">super_method</span> <span class="c1"># => nil (wrong, should be <UnboundMethod: <something>(A)#foo>)</span>
<span class="no">C</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">method</span><span class="p">(</span><span class="ss">:foo</span><span class="p">).</span><span class="nf">super_method</span> <span class="c1"># => #<Method: Object(A)#foo> (ok)</span>
<span class="no">D</span><span class="p">.</span><span class="nf">instance_method</span><span class="p">(</span><span class="ss">:foo</span><span class="p">).</span><span class="nf">super_method</span> <span class="c1"># => #<UnboundMethod: Object(B)#foo> (ok)</span>
<span class="no">D</span><span class="p">.</span><span class="nf">instance_method</span><span class="p">(</span><span class="ss">:foo</span><span class="p">).</span><span class="nf">super_method</span><span class="p">.</span><span class="nf">super_method</span> <span class="c1"># => #<UnboundMethod: Object(A)#foo> (ok)</span>
</code></pre>
<p>Note that the results for C and D's super_method differ slightly, with one outputing "C(B)" and the other "Object(B)". I don't understand why "Object" shows anywhere in my example. I would have expected the output to be "D(B)" in the later case. Should I open a different issue for this?</p> Ruby master - Feature #11816 (Assigned): Partial safe navigation operatorhttps://bugs.ruby-lang.org/issues/118162015-12-14T18:19:37Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I'm extremely surprised (and disappointed) that, currently:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">x</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="n">x</span><span class="o">&</span><span class="p">.</span><span class="nf">foo</span><span class="p">.</span><span class="nf">bar</span> <span class="c1"># => NoMethodError: undefined method `bar' for nil:NilClass</span>
</code></pre>
<p>To make it safe, you have to write <code>x&.foo&.bar</code>. But if <code>foo</code> is never supposed to return <code>nil</code>, then that code isn't "fail early" in case it actually does. <code>nil&.foo.bar</code> is more expressive, simpler and is perfect if you want to an error if <code>foo</code> returned <code>nil</code>. To actually get what you want, you have to resort using the old form <code>x && x.foo.bar</code>...</p>
<p>In CoffeeScript, you can write <code>x()?.foo.bar</code> and it will work well, since it gets compiled to</p>
<pre><code class="js syntaxhl" data-language="js"><span class="k">if </span><span class="p">((</span><span class="nx">_ref</span> <span class="o">=</span> <span class="nf">x</span><span class="p">())</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">_ref</span><span class="p">.</span><span class="nx">foo</span><span class="p">.</span><span class="nx">bar</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
<p>All the discussion in <a class="issue tracker-2 status-5 priority-4 priority-default closed" title="Feature: Introduce "Safe navigation operator" (Closed)" href="https://bugs.ruby-lang.org/issues/11537">#11537</a> focuses on <code>x&.foo&.bar</code>, so I have to ask:</p>
<p>Matz, what is your understanding of <code>x&.foo.bar</code>?</p>
<p>I feel the current implementation is not useful and should be changed to what I had in mind. I can't see any legitimate use of <code>x&.foo.bar</code> currently.</p> Ruby master - Bug #11776 (Closed): dig and custom objectshttps://bugs.ruby-lang.org/issues/117762015-12-06T05:18:02Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Although currently undocumented and untested, it is possible to <code>dig</code> objects of any class that implements <code>dig</code>:</p>
<pre><code>class Foo
def dig(x, *)
40 + x
end
end
{hello: Foo.new}.dig(:hello, 2) # => 42
</code></pre>
<p>This seems actually quite nice to me.</p>
<p>Matz, could you confirm that this is part of the new feature? I'll fix the documentation and add some basic tests</p> Ruby master - Bug #10831 (Closed): Variable keyword arguments shouldn't create immortal symbolshttps://bugs.ruby-lang.org/issues/108312015-02-05T19:58:53Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Calling a method with keyword arguments will, sometimes, create immortal symbols.</p>
<p>The following tests should not fail:</p>
<pre><code>def test_kwarg_symbol_leak_no_rest
foo = -> (arg: 42) {}
assert_no_immortal_symbol_created("kwarg no rest") do |name|
assert_raise(ArgumentError) { foo.call(name.to_sym => 42) }
end
end
def test_kwarg_symbol_leak_with_rest
foo = -> (arg: 2, **options) {}
assert_no_immortal_symbol_created("kwarg with rest") do |name|
foo.call(name.to_sym => 42)
end
end
def test_kwarg_symbol_leak_just_rest
foo = -> (**options) {}
assert_no_immortal_symbol_created("kwarg just rest") do |name|
foo.call(name.to_sym => 42)
end
end
</code></pre>
<p>Note: the last one succeeds (because the hash is simply cloned internally), and is there for completeness.</p> Ruby master - Bug #10828 (Closed): send should not create immortal symbolshttps://bugs.ruby-lang.org/issues/108282015-02-04T19:01:50Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>While <code>public_send</code> is ok, <code>send</code> and <code>__send__</code> create immortal symbols when they shouldn't.</p> Ruby master - Bug #9660 (Closed): test/unit, minitest & bundlerhttps://bugs.ruby-lang.org/issues/96602014-03-21T19:15:10Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>test/unit now calls <code>gem 'minitest'</code>, but this will create regressions for anyone using <code>bundler</code>.</p>
<p>For example, create an empty <code>Gemfile</code> and try <code>bundle exec ruby -e "require 'test/unit'"</code></p>
<p>You get an error:</p>
<pre><code>.rvm/gems/ruby-head@global/gems/bundler-1.5.3/lib/bundler/rubygems_integration.rb:240:in `block in replace_gem': minitest is not part of the bundle. Add it to Gemfile. (Gem::LoadError)
</code></pre>
<p>See: <a href="https://github.com/ruby/ruby/commit/da61291a25faae95f33de6756b2eaa4804d5ef2b#commitcomment-5761129" class="external">https://github.com/ruby/ruby/commit/da61291a25faae95f33de6756b2eaa4804d5ef2b#commitcomment-5761129</a></p> Ruby master - Feature #9347 (Open): Accept non callable argument to detecthttps://bugs.ruby-lang.org/issues/93472014-01-03T07:37:33Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Currently, the only argument that <code>Enumerable#detect</code> accepts is a callable object.</p>
<p>Shouldn't we accept non callable objects too?</p>
<pre><code>[42].detect(:not_found){} # => NoMethodError: undefined method `call' for :not_found:Symbol
# would return :not_found instead.
</code></pre>
<p>I'd suggest that if the given argument does not <code>respond_to? :call</code>, then it would be returned as is instead of raising an error as currently.<br>
Wouldn't this be more flexible and possibly more performant?</p>
<p>Inspired by <a href="http://stackoverflow.com/questions/20883414/why-does-enumerabledetect-need-a-proc-lambda" class="external">http://stackoverflow.com/questions/20883414/why-does-enumerabledetect-need-a-proc-lambda</a></p> Ruby master - Bug #9242 (Closed): Rdoc detection of aliaseshttps://bugs.ruby-lang.org/issues/92422013-12-12T04:46:38Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>The online doc appears to be making bad detection of aliases.</p>
<p>For example, Array#map and Array#collect are not marked as aliases (on either ruby-doc.org or docs.ruby-lang.org)</p>
<p>On the other hand, when aliases are detected, the generated method interface and the name of the method it is aliases to are always the same for both methods.</p>
<p>For example, the doc for Array#find_index says it is an alias to find_index and has interface showing index (again on both sites), e.g. <a href="http://docs.ruby-lang.org/en/2.0.0/Array.html#method-i-find_index" class="external">http://docs.ruby-lang.org/en/2.0.0/Array.html#method-i-find_index</a></p>
<p>It might also be a good idea to always have doc examples using both forms in case of aliases.</p> Ruby master - Bug #9048 (Closed): Remove legacy ±(binary) special cases.https://bugs.ruby-lang.org/issues/90482013-10-24T00:30:13Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Is there any reason not to get rid of the following special cases?</p>
<pre><code>'+(binary)'.to_sym # => :+ when expected :"+(binary)"
</code></pre>
<p>The following patch didn't reveal any failure in make test:</p>
<p>diff --git a/parse.y b/parse.y<br>
index 76fc9e7..6550235 100644<br>
--- a/parse.y<br>
+++ b/parse.y<br>
@@ -10045,8 +10045,6 @@ static const struct {<br>
} op_tbl[] = {<br>
{tDOT2, ".."},<br>
{tDOT3, "..."},</p>
<ul>
<li>{'+', "+(binary)"},</li>
<li>{'-', "-(binary)"},<br>
{tPOW, "<strong>"},<br>
{tDSTAR, "</strong>"},<br>
{tUPLUS, "+@"},<br>
diff --git a/test/ruby/test_m17n.rb b/test/ruby/test_m17n.rb<br>
index 7c37c8a..3ea346f 100644<br>
--- a/test/ruby/test_m17n.rb<br>
+++ b/test/ruby/test_m17n.rb<br>
@@ -1230,7 +1230,7 @@ class TestM17N < Test::Unit::TestCase</li>
</ul>
<p>def test_symbol_op<br>
ops = %w"</p>
<ul>
<li>
<pre><code> .. ... + - +(binary) -(binary) * / % ** +@ -@ | ^ & ! <=> > >= < <= ==
</code></pre>
</li>
</ul>
<ul>
<li>
<pre><code> .. ... + - * / % ** +@ -@ | ^ & ! <=> > >= < <= ==
=== != =~ !~ ~ ! [] []= << >> :: `
</code></pre>
"<br>
ops.each do |op|</li>
</ul> Ruby master - Bug #8894 (Closed): Fixnum#quo returns wrong result when given a floathttps://bugs.ruby-lang.org/issues/88942013-09-11T13:07:36Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Fixnum#quo is buggy.</p>
<p>2.quo(2.0) # => Rational(2, 2)</p>
<ol>
<li>Should return a float, not a rational</li>
<li>Moreover, that rational is invalid as it is not reduced.</li>
</ol>
<p>Noticed by David MacMahon <a href="/issues/8883">[ruby-core:57121]</a></p> Ruby master - Bug #8841 (Closed): Module#included_modules and prepended moduleshttps://bugs.ruby-lang.org/issues/88412013-08-31T08:17:39Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>The documentation for Module#included_modules currently states "Returns the list of modules included in +mod+."</p>
<p>This was never perfectly accurate, as the list also contains modules included in +mod+'s ancestors.</p>
<p>It now also includes prepended modules.</p>
<p>This is consistent with <code>include?</code> that returns true for prepended modules, but not quite consistent with <code>included</code> that does not get called for prepended modules.</p>
<p>Matz, could you confirm that current behavior is what you want?</p>
<p>If so, we should fix the documentation of <code>include?</code> and <code>included_modules</code>.</p> Ruby master - Bug #8162 (Closed): Documentation for trust/taint lackinghttps://bugs.ruby-lang.org/issues/81622013-03-25T11:04:42Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>If would be good to make the documentation for {un}{trust|taint}{ed?} clearer. c.f. <a href="https://blade.ruby-lang.org/ruby-core/53679">[ruby-core:53679]</a></p> Ruby master - Bug #8161 (Closed): String#+ should inherit untrustednesshttps://bugs.ruby-lang.org/issues/81612013-03-25T11:02:41Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>As noted by Nikolai Weibull <a href="https://blade.ruby-lang.org/ruby-core/53679">[ruby-core:53679]</a>, String#+ doesn't maintain untrustedness.</p>
<pre><code>s = "foo".untrust
(s * 2).untrusted? # => true
(s + s).untrusted? # => false, should be true
(s + '').untrusted? # => false, should also be true
</code></pre> Ruby master - Bug #8045 (Closed): Object#singleton_methods incompatible with prependhttps://bugs.ruby-lang.org/issues/80452013-03-08T09:57:00Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Similar to <a class="issue tracker-1 status-5 priority-4 priority-default closed" title="Bug: Object#methods incompatible with prepend (Closed)" href="https://bugs.ruby-lang.org/issues/8044">#8044</a>, although implementation is independent:</p>
<pre><code>o=Object.new
def o.foo; end
o.singleton_methods(false) # => [:foo], ok
o.singleton_class.send :prepend, Enumerable
o.singleton_methods(false) # => [], should be [:foo]
</code></pre> Ruby master - Bug #8044 (Closed): Object#methods incompatible with prependhttps://bugs.ruby-lang.org/issues/80442013-03-08T09:49:35Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Object#methods incompatible with prepend:</p>
<pre><code>o = Object.new
def o.foo; end
o.methods(false) # => [:foo], ok
o.singleton_class.send :prepend, Enumerable
o.methods(false) # => [], should be [:foo]
</code></pre> Ruby master - Bug #8041 (Closed): Marshal incompatibility with prependhttps://bugs.ruby-lang.org/issues/80412013-03-08T08:13:21Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>=begin<br>
Marshal doesn't work for objects with prepended modules:</p>
<pre><code>o = Object.new
o.singleton_class.send :include, Enumerable
Marshal.load(Marshal.dump(o)) # => ok
o = Object.new
o.singleton_class.send :prepend, Enumerable
Marshal.load(Marshal.dump(o)) # => ArgumentError: Object does not refer to module
</code></pre>
<p>=end</p> Ruby master - Bug #7916 (Closed): Callback Module.used is not used...https://bugs.ruby-lang.org/issues/79162013-02-23T08:08:28Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Module.used was meant to be called when it is called with <code>using</code>, see r36596.</p>
<p>It's not called right now.</p> Ruby master - Bug #7780 (Closed): Marshal & YAML should deserialize only basic types by default.https://bugs.ruby-lang.org/issues/77802013-02-04T11:30:48Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>YAML is a wonderful, powerful and expressive format to serialize data in a human readable way.</p>
<p>It can be used, for example, to read and write nice configuration files, to store strings, numbers, dates & times in a hash.</p>
<p>YAML.load will, by default, instantiate any user class and set instance variables directly.</p>
<p>On the other hand, this can make apparently innocent code lead to major vulnerabilities, as was clearly illustrated by different exploits recently.</p>
<p>I feel YAML.load should, by default, be safe to use, for example by instantiating only known core classes.</p>
<p>The same can be said for Marshal, even though it would more rarely be used as a public interface to exchange data.</p>
<p>Maybe the following transition path could be taken:</p>
<ol>
<li>Have {YAML|Marshal}.load issue a warning (once) that next minor will only deserialize basic types.</li>
<li>Create {YAML|Marshal}.unsafe_load, which does the same thing as current <code>load</code>, without a warning of course.<br>
As these changes are compatible and extremely minor, I would like them to be considered for Ruby 2.0.0. They also make for a</li>
</ol>
<p>"Secure by default" is not a new concept.<br>
Rails 3.0 has XSS protection by default, for example. The fact that one needs to do extra processing like calling <code>raw</code> when that security needs to be bypassed makes XSS attacks less likely.<br>
I believe the typical use of Yaml.load is to load basic types.<br>
We should expect users to use the easiest solution, so that should be the safe way.<br>
If a tool makes the safe way of doing things the default, and makes it easy to do more complex deserializing (e.g. whitelisting some user classes), this can only lead to less vulnerabilities.</p>
<p>I hope nobody will take offence that I've tagged this issue as a "bug". The current behavior is as speced, but it can be argued that a design that is inherently insecure is a defect.</p> Ruby master - Bug #7765 (Closed): Proc#arity bug with optional argumenthttps://bugs.ruby-lang.org/issues/77652013-02-01T05:38:17Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>We have:</p>
<pre><code>Proc.new{|a, b, c, d, e|}.arity # => 5
</code></pre>
<p>Matz decided <a href="/issues/5694">[ruby-core:46515]</a> we also have:</p>
<pre><code>Proc.new{|a, b, c, d, e=0|}.arity # => 4
</code></pre>
<p>It looks like we currently have:</p>
<pre><code>Proc.new{|a, b=0, c, d, e|}.arity # => 1, should be same as above
</code></pre>
<p>Hopefully there won't be disagreement that this is a bug?</p>
<p>I'm asking in particular because there was a specific test committed by Yui to that effect. I hope this was just an oversight?</p>
<pre><code>assert_equal(0, proc{|x=0, y|}.arity) # Should be 1, not 0. test/ruby/test_proc.rb:67
</code></pre>
<p>My patch is ready and I will commit it unless there is objection.</p>
<p><a href="https://github.com/marcandre/ruby/compare/marcandre:trunk...marcandre:proc_curry" class="external">https://github.com/marcandre/ruby/compare/marcandre:trunk...marcandre:proc_curry</a></p> Ruby master - Bug #7757 (Closed): String#start_with? and end_with? should accept Regexphttps://bugs.ruby-lang.org/issues/77572013-01-31T01:22:26Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>As suggested by Ilya in <a class="issue tracker-1 status-5 priority-4 priority-default closed" title="Bug: String#start_with? and end_with? ignore arguments convertible to a String [PATCH] (Closed)" href="https://bugs.ruby-lang.org/issues/5536">#5536</a>, the following should work:</p>
<pre><code>"hello".start_with? /h/ # => true, not a TypeError
</code></pre> Ruby master - Bug #7755 (Closed): JSON::Generate#configure's argument conversionhttps://bugs.ruby-lang.org/issues/77552013-01-30T04:33:38Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I notice a tiny bug in ext/json/generator/generator.c:514</p>
<pre><code>tmp = rb_convert_type(opts, T_HASH, "Hash", "to_hash");
if (NIL_P(tmp)) tmp = rb_convert_type(opts, T_HASH, "Hash", "to_h");
if (NIL_P(tmp)) {
rb_raise(rb_eArgError, "opts has to be hash like or convertable into a hash");
}
opts = tmp;
</code></pre>
<p>Bug is that rb_convert_type never returns nil.</p>
<p>Either both rb_convert_type are changed to rb_check_convert_type, or these lines are simply replaced by:</p>
<pre><code>opts = rb_convert_type(opts, T_HASH, "Hash", "to_hash");
</code></pre>
<p>I'd recommend this last option, for consistency with the rest of MRI.</p> Ruby master - Bug #7728 (Closed): Range#bsearch on other Numerics?https://bugs.ruby-lang.org/issues/77282013-01-23T03:12:30Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Range#bsearch attempts to do something on generic Numeric classes.</p>
<p>I feel it is both useless and buggy:</p>
<pre><code>(Rational(-1,2)..Rational(9,4)).bsearch{|x| true} # => yields with 7/8 and 33/16
(Rational(-1,2)..Rational(9,4)).bsearch{|x| false} # => loops forever
(BigDecimal('0.5')..BigDecimal('2.25'))... # => same
</code></pre>
<p>I feel the current implementation (aside from the bugs) only makes sense on Integers.</p>
<p>Possible approaches:</p>
<ul>
<li>Rational</li>
</ul>
<ol>
<li>convert to float, or</li>
<li>bsearch accepts a "max iterations" parameter (which would be required for Rational), or</li>
<li>forbid altogether</li>
</ol>
<ul>
<li>BigDecimal</li>
</ul>
<ol>
<li>convert to float, or</li>
<li>look at the space of decimal numbers in the range without a higher precision than begin or end.</li>
</ol>
<p>Given the timeframe though, I recommend we raise a TypeError for both in 2.0.0 (or a NotImplemented if we decide what should be done).</p> Ruby master - Bug #7726 (Closed): bsearch should handle block result in a consistent wayhttps://bugs.ruby-lang.org/issues/77262013-01-22T18:52:35Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>The documentation states that the block of bsearch must<br>
return either true/false or a number.</p>
<p>If the block returns another object (other than nil), I feel that either</p>
<ol>
<li>It should be considered as 'truthy'</li>
<li>It should raise a TypeError</li>
</ol>
<p>Currently it does not raise an error and returns a result different than if <code>true</code> was passed:</p>
<p>(1..3).bsearch{ 'x' } == (1..3).bsearch{ true } # => false</p> Ruby master - Bug #7725 (Closed): bsearch should return enumerator when called without a blockhttps://bugs.ruby-lang.org/issues/77252013-01-22T18:48:44Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>bsearch should return enumerator when called without a block</p>
<p>This would allow, for example</p>
<p>values.bsearch.with_index{|val, index| val >= some_array[index]} # => a value</p>
<p>Otherwise, one must use zip/each_with_index and will get an array returned, not just the value seeked:</p>
<p>r = values.zip(some_array).bsearch{|val, other_val| val >= other_val}<br>
a_value = r.first</p> Ruby master - Bug #7724 (Closed): 6 bugs with Range#bsearch over floatshttps://bugs.ruby-lang.org/issues/77242013-01-22T18:47:35Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Take the following code, with from, to and search all Integer or all Float:</p>
<p>(from...to).bsearch{|f| f >= search}</p>
<p>I expected it to:<br>
0) never yield and return nil if range is empty, i.e. from >= to ("trivial test")</p>
<ol>
<li>never yield more than 65 times for floats<br>
or Math.log(to-from, 2)+1 for Integer ("log test")</li>
<li>return search if from <= search < to, nil otherwise ("coverage test")</li>
<li>never yield the same <code>f</code> ("uniqueness test")</li>
<li>if range is exclusive, never yield <code>to</code> nor return <code>to</code>; if range is inclusive and block returns false always yield <code>to</code> ("end of range test")</li>
<li>never yield a number < from or > to ("out of range test")</li>
</ol>
<p>These conditions are all respected for Integers but all can be broken for Floats<br>
Test 0, 1, 3, 4 & 5 fail even for small ordinary float values, while test 2 requires some larger floats, say 1e100 to fail.<br>
For example bsearch can yield over 2000 times (instead of at most 65).</p>
<p>These tests and a correct Ruby implementation of bsearch can be found here: <a href="https://github.com/marcandre/backports/compare/marcandre:master...marcandre:bsearch" class="external">https://github.com/marcandre/backports/compare/marcandre:master...marcandre:bsearch</a><br>
My implementation also passes the MRI tests.</p> Ruby master - Bug #7715 (Closed): Lazy enumerators should want to stay lazy.https://bugs.ruby-lang.org/issues/77152013-01-19T02:33:26Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I'm just waking up to the fact that many methods turn a lazy enumerator in a non-lazy one.</p>
<p>Here's an example from Benoit Daloze in <a href="/issues/6261">[ruby-core:44151]</a>:</p>
<p>lines = File.foreach('a_very_large_file').lazy<br>
.select {|line| line.length < 10 }<br>
.map {|line| line.chomp!; line }<br>
.each_slice(3)<br>
.map {|lines| lines.join(';').downcase }<br>
.take_while {|line| line.length > 20 }</p>
<p>That code will produce the right result but <em>will read the whole file</em>, which is not what is desired</p>
<p>Indeed, <code>each_slice</code> currently does not return a lazy enumerator :-(</p>
<p>To make the above code as intended, one must call <code>.lazy</code> right after the <code>each_slice(3)</code>. I feel this is dangerous and counter intuitive.</p>
<p>Is there a valid reason for this behavior? Otherwise, I would like us to consider returning a lazy enumerator for the following methods:<br>
(when called without a block)<br>
each_with_object<br>
each_with_index<br>
each_slice<br>
each_entry<br>
each_cons<br>
(always)<br>
chunk<br>
slice_before</p>
<p>The arguments are:</p>
<ul>
<li>fail early (much easier to realize one needs to call a final <code>force</code>, <code>to_a</code> or <code>each</code> than realizing that a lazy enumerator chain isn't actually lazy)</li>
<li>easier to remember (every method normally returning an enumerator returns a lazy enumerator). basically this makes Lazy covariant</li>
<li>I'd expect that if you get lazy at some point, you typically want to remain lazy until the very end</li>
</ul> Ruby master - Bug #7706 (Closed): Lazy#zip should not call `lazy`https://bugs.ruby-lang.org/issues/77062013-01-17T03:27:36Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Current implementation of Lazy#zip has problems:</p>
<ol>
<li>
<p>calls <code>lazy</code> when this is not excepted, necessary nor documented. Any reason to not call <code>each</code> instead?</p>
</li>
<li>
<p>calls <code>lazy</code> without checking previously for type:</p>
<p>[1].lazy.zip(42) # =>NoMethodError: undefined method `lazy' for 42:Fixnum</p>
<a name="expected-same-as-1zip42-ie-a-TypeError"></a>
<h1 >expected same as [1].zip(42), i.e. a TypeError<a href="#expected-same-as-1zip42-ie-a-TypeError" class="wiki-anchor">¶</a></h1>
</li>
<li>
<p>inefficient in the case where all arguments are arrays</p>
</li>
</ol>
<p>I'll address these when I get a chance. I don't understand why <code>lazy</code> is called instead of <code>each</code> though. Anyone?</p> Ruby master - Bug #7696 (Closed): Lazy enumerators with state can't be rewoundhttps://bugs.ruby-lang.org/issues/76962013-01-15T06:45:46Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>The 4 lazy enumerators requiring internal state, i.e. {take|drop}{_while}, don't work as expected after a couple <code>next</code> and a call to <code>rewind</code>.</p>
<p>For example:</p>
<pre><code>e=(1..42).lazy.take(2)
e.next # => 1
e.next # => 2
e.rewind
e.next # => 1
e.next # => StopIteration: iteration reached an end, expected 2
</code></pre>
<p>This is related to <a class="issue tracker-1 status-5 priority-4 priority-default closed" title="Bug: 3 bugs with Lazy enumerators with state (Closed)" href="https://bugs.ruby-lang.org/issues/7691">#7691</a>; the current API does not give an easy way to handle state.</p>
<p>Either there's a dedicated callback to rewind, or data must be attached to the yielder.</p> Ruby master - Bug #7692 (Closed): Enumerator::Lazy#drop_while and take_while should require a block.https://bugs.ruby-lang.org/issues/76922013-01-14T16:33:07Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Enumerator::Lazy#drop_while and take_while should require a block.</p>
<p>Currently:</p>
<pre><code>[1].lazy.drop_while.force # => LocalJumpError: no block given
[1].lazy.take_while.force # => LocalJumpError: no block given
</code></pre>
<p>After patch, these will raise an ArgumentError "tried to call lazy drop_while without a block"</p> Ruby master - Bug #7691 (Closed): 3 bugs with Lazy enumerators with statehttps://bugs.ruby-lang.org/issues/76912013-01-14T14:53:31Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I found the following 3 bugs with Lazy#drop, drop_while and zip:</p>
<pre><code>drop = (1..4).lazy.drop(2)
drop.to_a # => [3, 4]
drop.to_a # => [1, 2, 3, 4], should be same as above
drop_while = (1..4).lazy.drop_while(&:odd?)
drop_while.to_a # => [2, 3, 4]
drop_while.to_a # => [1, 2, 3, 4], should be same as above
zip = (1..2).lazy.zip([3, 4]) # or (3..4)
zip.to_a # => [[1, 3], [2, 4]]
zip.to_a # => [[1, nil], [2, nil]] should be same as above
</code></pre>
<p>I found them when writing a Ruby implementation of Enumerator::Lazy.</p>
<p>My implementation created the same bug with #take as described in <a href="https://bugs.ruby-lang.org/issues/6428" class="external">https://bugs.ruby-lang.org/issues/6428</a>.</p>
<p>This made me realize that the api of Lazy.new makes it near impossible to write most lazy enumerators requiring a state, as there is no general way to reset that state.</p>
<p>When looking at my code, I used a state for drop, drop_while and zip. A quick verification showed me that the MRI implementation also has the same bugs!gene</p>
<p>So the meta question is: should there not be a general way to initialize the state of the lazy enum?</p> Ruby master - Bug #7690 (Closed): Enumerable::Lazy#flat_map should not call eachhttps://bugs.ruby-lang.org/issues/76902013-01-13T11:30:38Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I would expect that</p>
<pre><code>array.flat_map{...} == array.lazy.flat_map{...}.force
</code></pre>
<p>This is not always the case:</p>
<pre><code>[1].flat_map{|i| {i => i} } # => [{1 => 1}], ok
[1].lazy.flat_map{|i| {i => i} }.force # => [[1, 1]], expected [{1 => 1}]
</code></pre>
<p>Note that Matz confirmed that it is acceptable to return straight objects instead of arrays for flat_map <a href="/issues/6155">[ruby-core:43365]</a></p>
<p>It looks like this was intended for nested lazy enumerators:</p>
<pre><code>[1].lazy.flat_map{|i| [i].lazy }.force # => [1]
</code></pre>
<p>I don't think that's the correct result, and it is different from a straight flat_map:</p>
<pre><code>[1].flat_map{|i| [i].lazy } # => [#<Enumerator::Lazy: [1]>]
</code></pre>
<p>This is caused by Lazy#flat_map calls each (while Enumerable#flat_map only looks for Arrays/object responding to to_ary).</p> Ruby master - Feature #7444 (Open): Array#product_sethttps://bugs.ruby-lang.org/issues/74442012-11-27T14:44:28Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>I'd like to propose <code>Array#product_set</code> to return the product set of arrays (aka cartesian product)</p>
<pre><code>deck = [1..13, %i(spades hearts diamond clubs)].product_set
# => <#Enumerator ...>
deck.first(2) # => [[1, :spades], [2, :spades]]
</code></pre>
<p><code>product_set</code> would return an enumerator if no block is given. It should raise an error if an element of the array is not an Enumerable, like Array#transpose or #zip do.</p>
<p>Although <code>Array.product</code> would be acceptable too, I feel that an instance method of array is best in the case, in the same way that <code>transpose</code> is an instance method and not a class method.</p>
<p>The name "product_set" is a correct mathematical term. Although the synonym "cartesian_product" would also be acceptable, I propose "product_set" because it is shorter and cute too. I feel it is even clearer than <code>product</code>; the first time I head of <code>product</code> I was convinced that <code>[2,3,7].product # => 42</code>.</p>
<p>Addressing objections raised in <a class="issue tracker-2 status-6 priority-4 priority-default closed" title="Feature: Array::zip (Rejected)" href="https://bugs.ruby-lang.org/issues/6499">#6499</a>:</p>
<ol>
<li>This is not for the sake of symmetry, but because often we have an array of the arrays we want a product of.</li>
</ol>
<p>It is cumbersome to write <code>arrays.first.product(*arrays[1..-1])</code> or similar and it hides what is going on.</p>
<p>Writing <code>arrays.product_set</code> is much nicer.</p>
<ol start="2">
<li>
<p>The goal is not mainly to get a lazy version, but more to make the API better. The fact that it returns an Enumerator if no block is given is just a bonus :-)</p>
</li>
<li>
<p>[].product_set.to_a # => [[]]</p>
</li>
</ol>
<p>This can be seen from a cardinality argument, or for example because <code>array.repeated_permutation(n) == Array.new(n, array).product_set.to_a</code> and <code>array.repeated_permutation(0) == [[]]</code>.</p> Ruby master - Bug #7248 (Closed): Shouldn't Enumerator::Lazy.new be private?https://bugs.ruby-lang.org/issues/72482012-10-31T13:23:13Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Is there a reason why Enumerator::Lazy.new is not private?</p>
<p>Lazy enumerators should be created with <code>Enumerable#lazy</code>. Moreover, there is no doc, and it can give unexpected results too.</p> Ruby master - Bug #6658 (Closed): Module#ancestors & prependhttps://bugs.ruby-lang.org/issues/66582012-06-28T03:16:47Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Wouldn't it be best if <code>Module#ancestors</code> returned the modules & classes in the order they will be looked at?</p>
<p>Currently:</p>
<pre><code>module M; end
class C; prepend M; end
C.ancestors # => [C, M, Class, Object, Kernel, BasicObject]
# even though actual lookup order is [M, C, Class, Object, Kernel, BasicObject]
</code></pre> Ruby master - Bug #6120 (Closed): Float and BigDecimal bug in remainder in corner caseshttps://bugs.ruby-lang.org/issues/61202012-03-07T14:51:41Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Currently:</p>
<pre><code>4.2.remainder(+Float::INFINITY) # => 4.2, ok
4.2.remainder(-Float::INFINITY) # => NaN, should be 4.2
# (same with all signs reversed)
</code></pre>
<p>Reasons the remainder should be 4.2 and not NaN:</p>
<ol>
<li>foo.remainder(bar) == foo.remainder(-bar)</li>
<li>foo.remainder(bar) == foo when bar.abs > foo.abs</li>
</ol>
<p>Similarly:<br>
require 'bigdecimal'<br>
bd = BigDecimal.new("4.2")<br>
bd.remainder(BigDecimal.new("+Infinity")) # => NaN, should be bd<br>
bd.remainder(BigDecimal.new("-Infinity")) # => NaN, should be bd<br>
# (same with all signs reverse)</p>
<p>Reasons: same as float.</p>
<p>Finally:<br>
bd = BigDecimal.new("4.2")<br>
bd.modulo(BigDecimal.new("0")) # => ZeroDivisionError, probably ok?<br>
bd.remainder(BigDecimal.new("0")) # => NaN, should be probably raise a ZeroDivisionError?</p>
<p>Like in <a class="issue tracker-4 status-5 priority-4 priority-default closed" title="Backport: Float#% bug in cornercases (Closed)" href="https://bugs.ruby-lang.org/issues/6044">#6044</a>, this could be decided either way, as long as there is consistency. Anyone prefer NaN to raising a ZeroDivisionError?</p> Ruby master - Bug #6087 (Closed): How should inherited methods deal with return values of their o...https://bugs.ruby-lang.org/issues/60872012-02-26T06:02:37Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Just noticed that we still don't have a consistent way to handle return values:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">A</span> <span class="o"><</span> <span class="no">Array</span>
<span class="k">end</span>
<span class="n">a</span> <span class="o">=</span> <span class="no">A</span><span class="p">.</span><span class="nf">new</span>
<span class="n">a</span><span class="p">.</span><span class="nf">flatten</span><span class="p">.</span><span class="nf">class</span> <span class="c1"># => A</span>
<span class="n">a</span><span class="p">.</span><span class="nf">rotate</span><span class="p">.</span><span class="nf">class</span> <span class="c1"># => Array</span>
<span class="p">(</span><span class="n">a</span> <span class="o">*</span> <span class="mi">2</span><span class="p">).</span><span class="nf">class</span> <span class="c1"># => A</span>
<span class="p">(</span><span class="n">a</span> <span class="o">+</span> <span class="n">a</span><span class="p">).</span><span class="nf">class</span> <span class="c1"># => Array</span>
</code></pre>
<p>Some methods are even inconsistent depending on their arguments:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">a</span><span class="p">.</span><span class="nf">slice!</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">).</span><span class="nf">class</span> <span class="c1"># => A</span>
<span class="n">a</span><span class="p">.</span><span class="nf">slice!</span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="mi">0</span><span class="p">).</span><span class="nf">class</span> <span class="c1"># => A</span>
<span class="n">a</span><span class="p">.</span><span class="nf">slice!</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">).</span><span class="nf">class</span> <span class="c1"># => Array</span>
<span class="n">a</span><span class="p">.</span><span class="nf">slice!</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">).</span><span class="nf">class</span> <span class="c1"># => Array</span>
<span class="n">a</span><span class="p">.</span><span class="nf">slice!</span><span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">0</span><span class="p">).</span><span class="nf">class</span> <span class="c1"># => Array</span>
</code></pre>
<p>Finally, there is currently no constructor nor hook called when making these new copies, so they are never properly constructed.</p>
<p>Imagine this simplified class that relies on <code>@foo</code> holding a hash:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">A</span> <span class="o"><</span> <span class="no">Array</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
<span class="k">super</span>
<span class="vi">@foo</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">initialize_copy</span><span class="p">(</span><span class="n">orig</span><span class="p">)</span>
<span class="k">super</span>
<span class="vi">@foo</span> <span class="o">=</span> <span class="vi">@foo</span><span class="p">.</span><span class="nf">dup</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">a</span> <span class="o">=</span> <span class="no">A</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">flatten</span>
<span class="n">a</span><span class="p">.</span><span class="nf">class</span> <span class="c1"># => A</span>
<span class="n">a</span><span class="p">.</span><span class="nf">instance_variable_get</span><span class="p">(</span><span class="ss">:@foo</span><span class="p">)</span> <span class="c1"># => nil, should never happen</span>
</code></pre>
<p>I feel this violates object orientation.</p>
<p>One solution is to always return the base class (<code>Array</code>/<code>String</code>/...).</p>
<p>Another solution is to return the current subclass. To be object oriented, I feel we must do an actual <code>dup</code> of the object, including copying the instance variables, if any, and calling <code>initialize_copy</code>. Exceptions to this would be (1) explicit documentation, e.g. <code>Array#to_a</code>, or (2) methods inherited from a module (like <code>Enumerable</code> methods for <code>Array</code>).</p>
<p>I'll be glad to fix these once there is a decision made on which way to go.</p> Ruby master - Bug #6085 (Closed): Treatment of Wrong Number of Argumentshttps://bugs.ruby-lang.org/issues/60852012-02-25T15:47:19Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>For brevity, let me abbreviate:</p>
<pre><code>WNA = "wrong number of arguments"
</code></pre>
<p>Ruby could provide more accurate information when raising an ArgumentError for WNA.</p>
<p>Example:</p>
<p>def foo(a, b=42); end<br>
foo # => WNA (0 for 1)<br>
for(1,2,3) # => WNA (3 for 2)</p>
<p>It would be strictly superior if the message said instead "WNA (0 for 1..2)" and "WNA (3 for 1..2)":</p>
<ul>
<li>
<p>more useful as it gives more information at a glance</p>
</li>
<li>
<p>consistent with calling builtin methods:</p>
<p>"".index # => WNA (0 for 1..2)<br>
"".index(1,2,3) # => WNA (3 for 1..2)</p>
</li>
</ul>
<p>Ruby is also not always consistent in its wording when there is a *rest argument:</p>
<p>Enumerator.new # => WNA (0 for 1+)<br>
[].insert # => WNA (at least 1)</p>
<p>File.chown # => WNA (0 for 2+)<br>
Process.kill # => WNA (0 for at least 2)</p>
<p>While reviewing and factorizing all WNA errors, I also found a problematic case:</p>
<p>"".sub(//) # => WNA (1 for 1..2)</p>
<p>It would probably less confusing if it said (1 for 2), as the form without a block requires 2 parameters. Same applies to <code>sub!</code></p>
<p>Also, <code>Module#define_method</code> could say "WNA (3 for 1)" when it actually accepts only up to 2 arguments.</p>
<p>I've implemented two patches that address all these issues.</p>
<p>The first one improves the error message when calling user methods and lambdas.</p>
<p>The second harmonizes the builtin methods and fixes the few that need to be fixed.</p>
<p>The two commits can be found here:</p>
<p><a href="https://github.com/marcandre/ruby/commits/rb_arity_check" class="external">https://github.com/marcandre/ruby/commits/rb_arity_check</a></p>
<p>Complete list of changes:</p>
<ul>
<li>
<p>Improvements:</p>
<p>"".sub(//): WNA (1 for 1..2) => WNA (1 for 2)<br>
(same with sub)<br>
Module#define_method: WNA (3 for 1) => WNA (3 for 1..2)<br>
exec: WNA => WNA (0 for 1+)<br>
Hash.new(1){}: WNA => WNA (1 for 0)<br>
instance_eval("", "", 1, 2)<br>
WNA instance_eval(...) or instance_eval{...}<br>
=> WNA (4 for 1..3)<br>
(same with module_eval and class_eval)<br>
Module#mix: WNA (4 for 1) => WNA (4 for 1..3)<br>
Module#mix, with incorrect arguments: WNA (2 for 1) => wrong arguments</p>
</li>
</ul>
<p>Wording change:</p>
<ul>
<li>
<p>Change of language: WNA (at least 1) => WNA (0 for 1+)<br>
[].insert<br>
extend<br>
"".delete!<br>
"".count</p>
</li>
<li>
<p>Process.kill: WNA (0 for at least 2) => WNA (0 for 2+)</p>
</li>
</ul>
<p>Also, builtin functions calling <code>rb_scan_args</code> with both optional arguments and a rest argument would generate an error of the form "WNA (0 for 2..3+)". After this patch, this would now read "WNA (0 for 2+)", again for consistency. The only two such cases I found are in <code>ext/win32ole.c</code></p>
<p>In addition to giving a more consistent error handling, these commits pave the way to:</p>
<ul>
<li>improved error reporting for parameters with named parameters (forthcoming issue)</li>
<li>improved checking for Proc#curry (see bug <a class="issue tracker-1 status-5 priority-4 priority-default closed" title="Bug: Proc#curry doesn't always detect too many arguments (Closed)" href="https://bugs.ruby-lang.org/issues/5747">#5747</a>)</li>
</ul> Ruby master - Bug #6048 (Closed): {Unbound}Method#hash doesn't always return the right valuehttps://bugs.ruby-lang.org/issues/60482012-02-20T15:19:08Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>{Unbound}Method#hash doesn't always return the right value.</p>
<pre><code>map, collect = Array.instance_method(:map), Array.instance_method(:collect)
map.eql?(collect) # => true
map.hash == collect.hash # => false
</code></pre>
<p>I'm tempted to think that this is an obvious bug with an obvious solution but let me state:</p>
<p>As per the documentation and the design of hash tables, if two objects are <code>eql?</code> then they must have the same hash.</p>
<p>Either <code>map</code> should not be <code>eql?</code> to <code>collect</code> or else their <code>hash</code> should be the same.</p>
<p>As they are identical methods, it is correct that they are <code>eql?</code>, so <code>hash</code> must return the same value.</p>
<p>My proposed behavior passes my strict superiority test and is also "straightforward" as I could find no intent for the current behavior.</p>
<p>One solution is to ensure that all aliased methods are defined using <code>rb_define_alias</code>, which appears to yield the same hash. Another is to compute the <code>hash</code> correctly. Maybe there is a third approach.</p>
<p>I'm not super confident I took the right approach, but here is a patch for the second one. I think it is more robust and more consistent with how <code>{Unbound}Method.eql?</code> is implemented. It may also fix other cases of mismatch between <code>eql?</code> and <code>hash</code>, I didn't investigate further.</p>
<p>I'd be grateful if someone could review the patch (Koichi?) and let me know if I took the right approach and if I put things in the right place.</p>
<a name="Thanks"></a>
<h2 >Thanks<a href="#Thanks" class="wiki-anchor">¶</a></h2>
<p>Marc-André</p> Ruby master - Bug #6029 (Closed): Should OpenStruct implement #eql? and #hash?https://bugs.ruby-lang.org/issues/60292012-02-15T12:33:15Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Currently, OpenStruct have no specialized #eql? and #hash, so:</p>
<pre><code>require 'ostruct'
x = OpenStruct.new(foo: 42)
y = x.dup
x == y # => true
x.eql?(y) # => false
</code></pre>
<p>This means that OpenStruct does not behave like Struct, Array, Hash and other structures by comparing its fields and values.</p>
<p>This also prevents using OpenStructs as hash keys (unless one uses the exact same object as the key), contrary to other structures.</p>
<p>Is there an historical reason for this?</p>
<p>How likely would it be that adding #eql? and #hash create conflict with some fields?</p> Ruby master - Bug #6028 (Closed): OpenStruct.dup doesn't have all its methodshttps://bugs.ruby-lang.org/issues/60282012-02-15T11:44:57Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Dupped OpenStructs don't have the same methods.</p>
<pre><code>x = OpenStuct.new(foo: 42)
x.dup.methods == x.methods # => false
x.respond_to?(:foo) # => true
x.dup.respond_to?(:foo) # => false
</code></pre> Ruby master - Bug #5782 (Closed): File.binwrite doc and hash argumenthttps://bugs.ruby-lang.org/issues/57822011-12-20T16:14:02Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>The doc for <code>File.binwrite</code> doesn't mention the possibility of a fourth argument (option hash), unlike <code>File.write</code>. Judging from the code, this looks like an oversight.</p>
<p>Can we get confirmation that it does accept a fourth parameter and that the doc should be updated?</p>
<p>I would personally simplify the rdoc to state that it <code>File.binwrite</code> does exactly the same as <code>File.write</code> except that it opens the file in binary mode.</p>
<p>Noticed by Konstantin Haase.</p>
<a name="Thanks"></a>
<h2 >Thanks,<a href="#Thanks" class="wiki-anchor">¶</a></h2>
<p>Marc-Andre</p> Ruby master - Bug #5747 (Closed): Proc#curry doesn't always detect too many arguments https://bugs.ruby-lang.org/issues/57472011-12-12T09:30:21Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<p>Currently, Proc#curry checks the validity of the <code>arity</code> argument and will raise an error if:</p>
<ol>
<li>arity is less than the number of required arguments or</li>
<li>arity is more than the maximum number of arguments<br>
Note that simple Procs always accept any number of arguments (even though they may be ignored), so (2) doesn't applies to them, only to lambda's and procs made from methods.</li>
</ol>
<p>#2 isn't done as well as it could in case of limited optional parameters:</p>
<pre><code>def Object.foo(a, b=42); end
def Object.bar(a, b); end
Object.method(:foo).to_proc.curry(3) # => curried proc which will raise an error only when passed it's 3rd parameter
Object.method(:bar).to_proc.curry(3) # => ArgumentError: wrong number of arguments (3 for 2)
Same thing with lambdas
</code></pre>
<p>My proposed fix passes SST:<br>
a) usefulness: "fail-early" principle <a href="https://blade.ruby-lang.org/ruby-core/24130">[ruby-core:24130]</a><br>
b) consistency: both methods can only accept a maximum of 2 parameters and are thus treated consistently when attempting to curry with more than 2 arguments<br>
c) intuitiveness and d) performance: similar</p>
<p>It is straightforward (but not obvious), as it passes NIT (but not ODT).</p>