ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
* [ruby-core:102067] [Ruby master Bug#17537] === on ranges of strings is not consistant with include?
@ 2021-01-13 17:12 akim.demaille
  2021-01-13 17:35 ` [ruby-core:102068] " zverok.offline
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: akim.demaille @ 2021-01-13 17:12 UTC (permalink / raw)
  To: ruby-core

Issue #17537 has been reported by akim (Akim Demaille).

----------------------------------------
Bug #17537: === on ranges of strings is not consistant with include?
https://bugs.ruby-lang.org/issues/17537

* Author: akim (Akim Demaille)
* Status: Open
* Priority: Normal
* ruby -v: ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin18]
* Backport: 2.5: UNKNOWN, 2.6: UNKNOWN, 2.7: UNKNOWN, 3.0: UNKNOWN
----------------------------------------
Hi,

In Ruby up to 2.6 both `("1".."12").include?("6")` and `("1".."12") ===  "6"` were true.  In 2.7 and 3.0, `include?` accepts `"6"`, but `===` does not.  This was very handy in `case`s.  Reading the documentation it is unclear to me whether this change was intentional.

```
$ cat /tmp/foo.rb
puts(("1".."12").include?("6"))
puts(("1".."12") === "6")
p(("1".."12").to_a)
$ ruby2.6 /tmp/foo.rb
true
true
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]
$ ruby2.7 /tmp/foo.rb
true
false
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]
$ ruby3.0 /tmp/foo.rb
true
false
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]
```

Cheers!



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

^ permalink raw reply	[flat|nested] 4+ messages in thread

* [ruby-core:102068] [Ruby master Bug#17537] === on ranges of strings is not consistant with include?
  2021-01-13 17:12 [ruby-core:102067] [Ruby master Bug#17537] === on ranges of strings is not consistant with include? akim.demaille
@ 2021-01-13 17:35 ` zverok.offline
  2021-01-14  4:55 ` [ruby-core:102084] " akim.demaille
  2021-01-14 10:21 ` [ruby-core:102088] " zverok.offline
  2 siblings, 0 replies; 4+ messages in thread
From: zverok.offline @ 2021-01-13 17:35 UTC (permalink / raw)
  To: ruby-core

Issue #17537 has been updated by zverok (Victor Shepelev).


It was intentional.

Before 2.6, `===` was using `include?` underneath, which had some undesirable consequences:
```ruby
case '2.5.2'
when '2.5'..'2.6'
  puts "It is between 2.5 and 2.6"
else
  puts "It is not"
end
```
This prints `"It is between 2.6 and 2.6"` in Ruby 2.7, but prints `"It is not"` on 2.5. It seems that "logically" the former is right.

So, in Ruby 2.6, `===` [was changed to use `cover?`](https://rubyreferences.github.io/rubychanges/2.6.html#range-uses-cover-instead-of-include) -- but, somehow, not for strings.
It was considered an oversight and was [changed in 2.7](https://rubyreferences.github.io/rubychanges/2.7.html#for-string).

```ruby
case "6"
when "1".."12"
# ...
```

...is somewhat semantically "wrong" (you are expecting to strings being compared by their numeric values), but strings are tricky, and I agree there are many edge cases which can be considered "weird"/"inconsistent", there are several tickets (I don't remember the numbers by heart) complaining about strings being between the range ends, but not in the range's items list, or vice versa.

----------------------------------------
Bug #17537: === on ranges of strings is not consistant with include?
https://bugs.ruby-lang.org/issues/17537#change-89929

* Author: akim (Akim Demaille)
* Status: Open
* Priority: Normal
* ruby -v: ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin18]
* Backport: 2.5: UNKNOWN, 2.6: UNKNOWN, 2.7: UNKNOWN, 3.0: UNKNOWN
----------------------------------------
Hi,

In Ruby up to 2.6 both `("1".."12").include?("6")` and `("1".."12") ===  "6"` were true.  In 2.7 and 3.0, `include?` accepts `"6"`, but `===` does not.  This was very handy in `case`s.  Reading the documentation it is unclear to me whether this change was intentional.

```
$ cat /tmp/foo.rb
puts(("1".."12").include?("6"))
puts(("1".."12") === "6")
p(("1".."12").to_a)
$ ruby2.6 /tmp/foo.rb
true
true
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]
$ ruby2.7 /tmp/foo.rb
true
false
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]
$ ruby3.0 /tmp/foo.rb
true
false
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]
```

Cheers!



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

^ permalink raw reply	[flat|nested] 4+ messages in thread

* [ruby-core:102084] [Ruby master Bug#17537] === on ranges of strings is not consistant with include?
  2021-01-13 17:12 [ruby-core:102067] [Ruby master Bug#17537] === on ranges of strings is not consistant with include? akim.demaille
  2021-01-13 17:35 ` [ruby-core:102068] " zverok.offline
@ 2021-01-14  4:55 ` akim.demaille
  2021-01-14 10:21 ` [ruby-core:102088] " zverok.offline
  2 siblings, 0 replies; 4+ messages in thread
From: akim.demaille @ 2021-01-14  4:55 UTC (permalink / raw)
  To: ruby-core

Issue #17537 has been updated by akim (Akim Demaille).


Ok.  Well, my personal opinion is that just to have some fancy way to handle version strings, ranges of strings have inconsistent semantics.  With to_a, they behave like their natural order is shortlex on some imaginary alphabet:

```
irb(main):005:0> ('a'..'aa').to_a
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "aa"]
```

but `===` behaves like it's using only the lexical order:

```
irb(main):001:1* case 'ANA'
irb(main):002:1* when 'A'..'Z' then puts 1
irb(main):003:1* else puts 2
irb(main):004:0> end
1
=> nil
```

If the point was to support version strings, of course that does not work properly in all the cases, just the simple ones.

```
irb(main):001:1* case '2.6.5'
irb(main):002:1* when '2.4'..'2.10'
irb(main):003:1*   puts 'matches'
irb(main):004:1* else
irb(main):005:1*   puts 'nope :('
irb(main):006:0> end
nope :(
=> nil
```

Version strings should be handled, well, with their specific semantics.  So you need ranges of Versions.

I'm sad that global consistency (ranges have a set-semantics, and === means ∋) was sacrificed for some nice-looking-broken-example.

Just to be clear: I agree that expecting "6" to be part of "1".."12" is weird (but I really don't think it is less weird to expect "ANA" to be part of "A".."Z".).  But at least the language was consistent: "6" is there when one iterates over "1".."12".

Cheers!

----------------------------------------
Bug #17537: === on ranges of strings is not consistant with include?
https://bugs.ruby-lang.org/issues/17537#change-89942

* Author: akim (Akim Demaille)
* Status: Open
* Priority: Normal
* ruby -v: ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin18]
* Backport: 2.5: UNKNOWN, 2.6: UNKNOWN, 2.7: UNKNOWN, 3.0: UNKNOWN
----------------------------------------
Hi,

In Ruby up to 2.6 both `("1".."12").include?("6")` and `("1".."12") ===  "6"` were true.  In 2.7 and 3.0, `include?` accepts `"6"`, but `===` does not.  This was very handy in `case`s.  Reading the documentation it is unclear to me whether this change was intentional.

```
$ cat /tmp/foo.rb
puts(("1".."12").include?("6"))
puts(("1".."12") === "6")
p(("1".."12").to_a)
$ ruby2.6 /tmp/foo.rb
true
true
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]
$ ruby2.7 /tmp/foo.rb
true
false
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]
$ ruby3.0 /tmp/foo.rb
true
false
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]
```

Cheers!



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

^ permalink raw reply	[flat|nested] 4+ messages in thread

* [ruby-core:102088] [Ruby master Bug#17537] === on ranges of strings is not consistant with include?
  2021-01-13 17:12 [ruby-core:102067] [Ruby master Bug#17537] === on ranges of strings is not consistant with include? akim.demaille
  2021-01-13 17:35 ` [ruby-core:102068] " zverok.offline
  2021-01-14  4:55 ` [ruby-core:102084] " akim.demaille
@ 2021-01-14 10:21 ` zverok.offline
  2 siblings, 0 replies; 4+ messages in thread
From: zverok.offline @ 2021-01-14 10:21 UTC (permalink / raw)
  To: ruby-core

Issue #17537 has been updated by zverok (Victor Shepelev).


>  ranges of strings have inconsistent semantics

That's an inherent property of string ranges, and of Range in general. 
Range in Ruby represents TWO things:
* sequence (enumeration) from begin to end (based on the notion of the "next value", e.g. `<Type>#succ`)
* continuous space between begin and end (based on the notion of the order, e.g. `<Type>#<=>`)

The discrepancy between two is always present (e.g. `1...3` covers `1.5` as a space, but doesn't include it as a sequence), and indeed, with Strings, it is most noticeable.

It is true, though, not only for ranges but for other string "math" too:
```ruby
str = 'b'
str < 'я' # => true
str = str.succ while str < 'я' # infinite cycle, the string will "inrease" to "z", then turns to "aa", and so on
```

That's because the notion of the "next string" is quite synthetic, it works for _some cases_, but in general, it is a very limited "convenience" feature.

On the other hand, string order is more or less generic. 
It is impossible to make them fully consistent, and introducing "special" cases for "number-alike strings", for example, is unwanted.

"Versions" example was not the _main_ reason for a change, it was just a counter-example for your "1".."10" one, to show that "what's intuitive" depends on the case. (And of course, both of our "intuitive" examples are better solved with better types: mine with `Gem::Version`, yours with `Numeric`)

----------------------------------------
Bug #17537: === on ranges of strings is not consistant with include?
https://bugs.ruby-lang.org/issues/17537#change-89946

* Author: akim (Akim Demaille)
* Status: Open
* Priority: Normal
* ruby -v: ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin18]
* Backport: 2.5: UNKNOWN, 2.6: UNKNOWN, 2.7: UNKNOWN, 3.0: UNKNOWN
----------------------------------------
Hi,

In Ruby up to 2.6 both `("1".."12").include?("6")` and `("1".."12") ===  "6"` were true.  In 2.7 and 3.0, `include?` accepts `"6"`, but `===` does not.  This was very handy in `case`s.  Reading the documentation it is unclear to me whether this change was intentional.

```
$ cat /tmp/foo.rb
puts(("1".."12").include?("6"))
puts(("1".."12") === "6")
p(("1".."12").to_a)
$ ruby2.6 /tmp/foo.rb
true
true
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]
$ ruby2.7 /tmp/foo.rb
true
false
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]
$ ruby3.0 /tmp/foo.rb
true
false
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]
```

Cheers!



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

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2021-01-14 10:21 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-01-13 17:12 [ruby-core:102067] [Ruby master Bug#17537] === on ranges of strings is not consistant with include? akim.demaille
2021-01-13 17:35 ` [ruby-core:102068] " zverok.offline
2021-01-14  4:55 ` [ruby-core:102084] " akim.demaille
2021-01-14 10:21 ` [ruby-core:102088] " zverok.offline

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).