https://redmine.ruby-lang.org/https://redmine.ruby-lang.org/favicon.ico?17113305112021-02-17T11:09:17ZRuby Issue Tracking SystemRuby master - Misc #17637: Endless ranges with `nil` boundary weird behaviorhttps://redmine.ruby-lang.org/issues/17637?journal_id=904612021-02-17T11:09:17Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul><li><strong>Related to</strong> <i><a class="issue tracker-1 status-5 priority-4 priority-default closed" href="/issues/14845">Bug #14845</a>: Endless Range with nil</i> added</li></ul> Ruby master - Misc #17637: Endless ranges with `nil` boundary weird behaviorhttps://redmine.ruby-lang.org/issues/17637?journal_id=904622021-02-17T11:10:50Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p>I believe that using <code>nil</code> as a signifier of the "open end" is a compromise due to Ruby's ranges polymorphism. You can have range from a string, from time, from date, from any custom comparable class, how you'd signify the "open end" in this case? In some statically typed language it probably could've been some <code>Infinity<Type></code>, but in Ruby... IDK, maybe the alternative would be some "generic" <code>Infinity</code> constant/special value (as incompatible with any type as <code>nil</code> is, but having different semantics), but it would be too large a change.</p> Ruby master - Misc #17637: Endless ranges with `nil` boundary weird behaviorhttps://redmine.ruby-lang.org/issues/17637?journal_id=904632021-02-17T14:29:36Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<ul></ul><p>It's not clear what you are proposing. If it is to restore previous behavior, this won't be acceptable because of compatibility.</p>
<p>Note: you should be using <code>size</code> (lazy), not <code>count</code> (typically exhaustive):</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="kp">nil</span><span class="p">).</span><span class="nf">size</span> <span class="c1"># => Infinity</span>
<span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="no">Float</span><span class="o">::</span><span class="no">INFINITY</span><span class="p">).</span><span class="nf">size</span> <span class="c1"># => Infinity</span>
</code></pre> Ruby master - Misc #17637: Endless ranges with `nil` boundary weird behaviorhttps://redmine.ruby-lang.org/issues/17637?journal_id=904652021-02-17T17:36:10Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul></ul><p>Hi, I proposed and implemented a endless range.</p>
<p>This is a trade-off between early failure and usability/consistency.<br>
While the feature is indeed error-prone in some cases, it is more consistent and useful.</p>
<p>It is possible to allow only <code>(1..)</code> and deny <code>(1..nil)</code>.<br>
In fact, <code>(1..nil)</code> used to raise an exception for a short period of development phase of Ruby 2.6.</p>
<p><a href="https://github.com/ruby/ruby/commit/48de2ea5f9b9067779acb0f7f76e5f879f2b42c0" class="external">https://github.com/ruby/ruby/commit/48de2ea5f9b9067779acb0f7f76e5f879f2b42c0</a></p>
<p>But, to create a conditionally endless range, we need to write <code>max ? (1..max) : (1..)</code> or <code>Range.new(1, max)</code> if <code>(1..nil)</code> is prohibited.<br>
The current behavior allows to just write <code>(1..max)</code>. Thus, it was reverted.</p>
<p>It is very difficult to change the behavior from now because of the compatibility issue.<br>
But as I recall, this is the third time for me to see this issue reported.<br>
(The first is <a class="issue tracker-1 status-5 priority-4 priority-default closed" title="Bug: Endless Range with nil (Closed)" href="https://redmine.ruby-lang.org/issues/14845">#14845</a>. I couldn't find the second but I think someone said it in GitHub comments or else.)<br>
If it is a major source of bugs, and if a conditionally endless range is very rare, I'm personally open for the change.</p> Ruby master - Misc #17637: Endless ranges with `nil` boundary weird behaviorhttps://redmine.ruby-lang.org/issues/17637?journal_id=904662021-02-17T18:13:36Zgud (gud gud)
<ul></ul><p>What I would like to have in a programming language is standard library explicitly designed.<br>
So my example from the description with range 0 to Nothing speaks for itself.</p>
<p><code>0..Float::INFINITY</code> is pretty intuitive but <code>0..nil</code> looks more like a bug (I understand this is personal view)</p>
<blockquote>
<p>But, to create a conditionally endless range, we need to write max ? (1..max) : (1..) or Range.new(1, max) if (1..nil) is prohibited.<br>
The current behavior allows to just write (1..max). Thus, it was reverted.</p>
</blockquote>
<p>I see, but on the opposite hand to handle this nil-case you need something like:</p>
<pre><code>lower = 0
upper = some_method(arg1, arg2)
raise ArgumentError unless upper
(lower..upper).each do { |s| some_method2(s) } # or unless upper
</code></pre>
<p>which isn't handy also.</p>
<p>This is all about trade-offs and I understand that, but having <code>(1..)</code> syntax only is way less implicit and is a good way to leave this functionality if it's really needed, but make a code base slightly cleaner and more intuitive.</p> Ruby master - Misc #17637: Endless ranges with `nil` boundary weird behaviorhttps://redmine.ruby-lang.org/issues/17637?journal_id=904882021-02-18T14:52:31ZDan0042 (Daniel DeLorme)
<ul></ul><p>mame (Yusuke Endoh) wrote in <a href="#note-4">#note-4</a>:</p>
<blockquote>
<p>It is possible to allow only <code>(1..)</code> and deny <code>(1..nil)</code>.</p>
</blockquote>
<p>+1 for that. I was surprised that <code>(1..nil)</code> is allowed. If you have <code>(1..x)</code> and x is nil, it seems more likely to me that it was unexpectedly nil rather than intended as conditionally endless range.<br>
Maybe something more explicit could be allowed, like <code>(1..:endless)</code>, then <code>(1..x||:endless)</code> would be possible without ambiguity.</p> Ruby master - Misc #17637: Endless ranges with `nil` boundary weird behaviorhttps://redmine.ruby-lang.org/issues/17637?journal_id=904922021-02-18T21:26:38ZEregon (Benoit Daloze)
<ul></ul><p>If we change anything here, it should probably be done with beginless Ranges too for consistency.<br>
And then we'd have 4 cases instead of 1 like in @mame's reply.<br>
Also <code>(..)</code> doesn't parse, one needs <code>(nil..)</code>.</p>
<p>I think it's not worth breaking compatibility,<br>
especially considering that third-party libraries most likely already rely on Range#{begin/end} == nil => beginless/endless.</p>
<p>How often does one actually get bugs based on this?<br>
I would guess it's pretty rare, most Ranges are on numeric values or Strings, and if you get a nil out of arithmetic, there is definitely something quite fishy in the code.<br>
It seems a good idea to validate <code>upper</code> in the example if it is potentially very high or not numeric, and the same would apply if upper is Float::INFINITY or 1.0/0.0.</p> Ruby master - Misc #17637: Endless ranges with `nil` boundary weird behaviorhttps://redmine.ruby-lang.org/issues/17637?journal_id=904942021-02-18T23:10:28ZDan0042 (Daniel DeLorme)
<ul></ul><p>What I was suggesting was to use a symbol when <em>creating</em> an endless range, but the <code>end</code> value would still be nil.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="p">).</span><span class="nf">end</span> <span class="c1">#=>nil</span>
<span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="kp">nil</span><span class="p">).</span><span class="nf">end</span> <span class="c1">#error</span>
<span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="ss">:endless</span><span class="p">).</span><span class="nf">end</span> <span class="c1">#=>nil</span>
</code></pre>
<p>It seems pretty easy to accidentally create an endless range, like <code>(1..values.max)</code> if values is empty. Until 2.6 this kind of validation was built-in so this would be <em>restoring</em> broken functionality; imho this is a bug fix. Although the fact there has been few bug reports in the last 2 years means this is not likely a large problem.</p>
<p>I'm very surprised that <code>(nil..nil)</code> is even valid.</p> Ruby master - Misc #17637: Endless ranges with `nil` boundary weird behaviorhttps://redmine.ruby-lang.org/issues/17637?journal_id=905062021-02-19T07:57:00Zgud (gud gud)
<ul></ul><blockquote>
<p>Also (..) doesn't parse, one needs (nil..).</p>
</blockquote>
<p>Wow, I think it's absolutely OK that <code>(..)</code> doesn't parse. Like is it Ruby or <a href="https://uk.wikipedia.org/wiki/Brainfuck" class="external">https://uk.wikipedia.org/wiki/Brainfuck</a><br>
And <code>(nil..)</code> what is that ? Like really if I saw that code 5 years ago without debugging I would say it shouldn't parse as well, my guess would be this is a bug.</p>
<p>My main point is: we already had <code>(0..Float::INFINITY)</code> in previous versions having that we had proper <code>ArgumentError</code> on <code>nil</code>s. So I am not sure why we had to expand the std library for new syntax which bring a lot of confusing e.g.:</p>
<pre><code>a = 1
b = 1
a == b => true
(a...b).size => 0
a = 'a'
b = 'a'
a == b => true
(a...b).size => 0
And now "special case"
a = nil
b = nil
a == b => true
(a...b).size => Infinity
</code></pre>
<p>And <code>(1..nil)</code> iterates for ever which I can read like <code>nil === Float::INFINITY</code> but actually this returns <code>false</code> and <code>nil.to_i</code> returns <code>0</code>, but <code>(0..nil.to_i).size</code> => 1</p>
<p>If this is not confusing for newcomers then what is ?</p>
<blockquote>
<p>I think it's not worth breaking compatibility,<br>
especially considering that third-party libraries most likely already rely on Range#{begin/end} == nil => beginless/endless.</p>
</blockquote>
<p>I am trying to think of a good use case for that stuff and I can't find an answer.</p>
<p>If you need an <code>infinite loop</code> you can always use well known <code>while true</code> or even <code>loop do</code> in Ruby. And those are more common and intuitive way to do such a thing.<br>
You can't iterate over <code>nil..nil</code> because really, what is that ?<br>
Sometimes it's good to revert the stuff even if it's already been using (like pipe operator), still I think the usage of it is not that common.</p>
<blockquote>
<p>How often does one actually get bugs based on this?</p>
</blockquote>
<p>Not so often but it happens and when it happens it creates a memory leak.</p>
<p>I had a <code>begin/rescue ArugmentError</code> around my code and had a feeling I was OK and then this happened. BOOM.</p>
<p>I also do have a feeling that regular usage of range e.g. <code>(lower..upper)</code> is really common in Ruby code, so making this syntax dangerous for a better usability of <code>conditional endless range</code> which I guess is really rare... feels wrong.</p>
<p>There is a lot of text and please don't get it personal but sometimes I guess this "syntax sugar" stuff drives Ruby in a wrong direction. Aliases/3-4 ways to do the same thing and so on.</p>