Cooperative equality in Spire

Skip to first unread message

Bill Venners

Jan 18, 2014, 12:32:02 AM1/18/14

I would like to learn more about the reasoning behind implementing co-operative equality among the various numeric types defined in Spire. Until today I was unaware that Scala did this as well. A BigInt can equal an Int and vice versa. It needs to be vice versa because of the equals contract, which requires symmetry. One thing I noticed in Spire is that the code didn't seem to be ensuring symmetry, and in some quick  I did find this one example:

scala> import spire.math._
import spire.math._

scala> val u = UInt(3)
u: spire.math.UInt = 3

scala> val n = Natural(3)
n: spire.math.Natural = 3

scala> u equals n
res0: Boolean = false

scala> n equals u
res1: Boolean = true

I suspect that could be made symmetric, because it should be possible to teach all of Spire's numeric types to recognize the other types to which they can be equal. I also noticed that == is overloaded in some places, leading to apparent inconsistencies, like:

scala> u == 3
res6: Boolean = true

scala> 3 == u
<console>:12: warning: comparing values of types Int and spire.math.UInt using `==' will always yield false
              3 == u
res7: Boolean = false

But this is just with the == operator, because it is overloaded on the UInt side. When using equals it is symmetric (though inconsistent with ==):

scala> u equals 3
res8: Boolean = false

scala> 3 equals u
res9: Boolean = false

So I'm also curious about what the goal of overloading == is. That seems like it would surprise users.

But mainly I was just surprised myself today that Scala was allowing Ints, BigInts, BigDecimals and others to equal each other. The reason is this is actually the first real use case I have ever seen that "needs" Java's universal equality, where anything can be compared with anything, including unrelated types that cooperate and allow themselves to equal each other. The lowest common supertype of Int and BigInt is Any, but they can equal each other. Because the type passed to equals is Any, you can ask an Int if it is equal to a BigInt, and vice versa.

Since Spire seems to be trying to do a similar kind of thing, letting diverse numeric types equal each other, I wanted to ask here why. Is this cooperative equality among numeric types something needed by specific use cases, something that seemed like it would be more convenient for users? Did you consider just letting Numerics equal Numerics, and UInts equal UInts, and then letting people call methods like .toUInt and .toNumeric? Maybe I just answered my own question, because you probably can't convert all Numerics to UInts. But regardless, I'd like to hear the actual motivation.



Erik Osheim

Jan 18, 2014, 10:53:02 AM1/18/14
to Bill Venners,
Hi Bill,

So I did some digging last night, and I just wanted to follow up on

It turns out that value classes in Scala get the worst of both worlds
right now. They can't participate in the built-in "universal equality"
(since they can only extend universal traits) so they don't have any
mechanism for making (1 == x) evaluate to true, even if the value
class "wraps" the value 1.

I got the overloading == and != from Martin's unsigned long example,
actually. Since value classes cannot implement equals, overloading is
the only mechanism available. I think Spire's big mistake was making
(x == 0) work, since we cannot make (0 == x) work.

I will be changing the value classes so that none of them try to
implement cooperative equality. I think this will be less confusing in
the long run.

-- Erik
Reply all
Reply to author
0 new messages