[#109207] [Ruby master Feature#18915] New error class: NotImplementedYetError or scope change for NotImplementedYet — Quintasan <noreply@...>
Issue #18915 has been reported by Quintasan (Michał Zając).
18 messages
2022/07/14
[ruby-core:109325] [Ruby master Bug#18937] Inconsistent definitions of Complex#<=> and Numeric#<=> with others
From:
"msnm (Masahiro Nomoto)" <noreply@...>
Date:
2022-07-26 13:39:04 UTC
List:
ruby-core #109325
Issue #18937 has been updated by msnm (Masahiro Nomoto).
@mame
I have a motivation to create a "quaternion" class which is highly interoperable with other built-in numeric classes. (gem: [quaternion_c2](https://rubygems.org/gems/quaternion_c2))
I recently noticed that there was `Complex#<=>` in Ruby >= 2.7 . I want to implement `Quaternion#<=>` though I believe few people compare complex numbers by `#<=>`. However, `Complex#<=>` feels odd and I can't define `Quaternion#<=>` well.
---
> `Complex#<=>` is a different method from `Object#<=>`, so I don't see that as a problem in itself. Some Ruby core methods don't necessarily follow the Liskov Substitution Princple. I think this is one example of such cases that we favors mathematical intuition over the principle. If you are facing any practical problem, please elaborate the situation.
Thanks. I changed my understanding: "(non-real) complex numbers are **not comparable**, then `Complex#<=>` for them always returns nil even if `self.equal?(other)`".
```ruby
Complex::I <=> Complex::I #=> nil
```
How about `Numeric#<=>` ? I think `num1 == num2` (equivalency) is more intuitive than `num1.equal?(num2)` (identity) in mathematical contexts. Another idea is that the method always returns nil in order to tell "the custom numeric class may not be comparable".
---
>> This prevents users from adding yet another complex class having #<=>.
>
> I understand this as follows.
That's right. Moreover, `MyInteger#<=>` will produce asymmetric (not antisymmetric) behaviors.
```ruby
class MyInteger
def initialize(n)
@n = n
end
def coerce(obj)
[obj, @n]
end
def <=>(obj)
if obj.kind_of?(MyInteger)
@n <=> obj.instance_variable_get(:@n)
else
@n <=> obj
end
end
end
my_int = MyInteger.new(2)
p 1 + 0i <=> my_int #=> nil (expected: -1)
p my_int <=> 1 + 0i #=> 1
```
----------------------------------------
Bug #18937: Inconsistent definitions of Complex#<=> and Numeric#<=> with others
https://bugs.ruby-lang.org/issues/18937#change-98469
* Author: msnm (Masahiro Nomoto)
* Status: Open
* Priority: Normal
* ruby -v: 3.1.2
* Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN
----------------------------------------
Object#<=> says "Returns 0 if `obj` and `other` are the same object or `obj == other`" .
https://ruby-doc.org/core-3.1.2/Object.html#method-i-3C-3D-3E
However, neither Complex#<=> nor Numeric#<=> satisfies that definition.
```ruby
num1 = Complex(0, 42)
num2 = Complex(0, 42)
p num1.equal?(num2) #=> false
p num1 == num2 #=> true
# using Complex#<=>
p num1 <=> num2 #=> nil
# using Numeric#<=>
Complex.remove_method(:<=>)
p num1 <=> num2 #=> nil
# using Object#<=> (Kernel#<=>)
Numeric.remove_method(:<=>)
p num1 <=> num2 #=> 0
```
Complex#<=> has another problem that it does not coerce numeric objects while Integer#<=> and Float#<=> do.
This prevents users from adding yet another complex class having #<=>.
---
Here is my proposal of Complex#<=> behavior (in Ruby).
This considers #15857, complex numbers are comparable when their imaginary parts are 0.
```ruby
class Complex
def <=>(other)
return (self == other ? 0 : nil) if self.imag != 0
if other.kind_of?(Complex)
if other.imag == 0
return self.real <=> other.real
else
return nil
end
elsif other.kind_of?(Numeric) && other.real?
return self.real <=> other
elsif other.respond_to?(:coerce)
num1, num2 = other.coerce(self)
return num1 <=> num2
else
return nil
end
end
end
```
--
https://bugs.ruby-lang.org/
Unsubscribe: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>