From: "msnm (Masahiro Nomoto)" Date: 2022-07-26T13:39:04+00:00 Subject: [ruby-core:109325] [Ruby master Bug#18937] Inconsistent definitions of Complex#<=> and Numeric#<=> with others 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: