简体   繁体   中英

What are the reasons and/or benefits Scala can compare incompatible objects?

This puzzles me -- I have read the reasons Scala exists at all, and the common-sense presented appeals to me, for example choosing static typing (because of less errors). Yet, you can compare (out of the box, by default) completely different, irrelevant objects and it compiles and runs fine. For me it just begs for more errors in code.

Could someone please explain what are the reasons for such feature? Or benefits?

I know how Scala works in the matter of comparison. I am asking WHY it works that way.

I would expect, that if I want to do this, I would write implicit conversion or explicit comparison. That approach makes perfect sense for me, current Scala way -- no, and thus my question.

And one more thing -- I am not looking how to use Scala-way comparison for some fancy effects, I am looking for more strict error checking. IOW: I don't want to compare Orange and Apple by color, I want to forbid such comparison by default unless user explicitly say it is OK to compare such types.

Example

class Test
{
  val s : String = "ala"
}

class Foo
{
  val x : Int = 5
}

object Testbed 
{
  def main(args : Array[String])
  {
    val t = new Test
    val f = new Foo
    if (t==f)
      println("match")
    else
      println("no")
  }
}

Well, the simple answer is that == is designed to be compatible with java.lang.Object.equals() . Since any class can override equals() it's impossible for the Scala compiler to determine the result of an equality check between two objects unless it knows the runtime class of the object at compile time (ie the static type must be a final class or the new invocation must be visible to the compiler when compiling the equality check) AND the equals() method is not overridden in this class or any super class.

So, in your example the compiler can indeed infer the runtime class of both t and f and issue a warning (not an error though) for the equality check, but in practice the cases where the runtime class can be inferred are quite rare. Note that scalac already issues warnings for some equality comparisons between primitive types and object types.

PS If you want a safer equality check use the Equal trait in Scalaz.

Equality in scala is value equality (not reference) and what is equal to what can be defined by you by overriding equals . Basically == is not an operator in scala, but acts like (and in fact IS) a method on the object that you want to test for equality. So t == f is actually t.==(f) where == is defined on Any , so you get it on every class in scala.

For instance (in your example) you can make your Test == to a Foo, like this:

class Test {
  val s : String = "ala"
}

class Foo {
  val x : Int = 5
  override def equals(that: Any) : Boolean = {
    that.isInstanceOf[Test] && this.x == 5 && that.asInstanceOf[Test].s=="ala";
  }  
}

and now you get:

scala> val t = new Test
t: Test = Test@86a58a

scala> val f = new Foo
f: Foo = Foo@104f889

scala> f==t
res3: Boolean = true

but (since we have NOT overridden equals on Test)

scala> t==f
res4: Boolean = false

While in this specific case this does not make a lot of sense, the point is that scala lets you decide what makes a Test equal to a Foo. Do you want a Person to be == to another Person (or to an Employee) if they have the same social security number? You can implement that logic.

However, with great power comes great responsibility and in fact the concept of equality is surprisingly tricky .

In recent trunk builds your example warns. I think I found a good balance between likelihood of mis-warning and catching unintended comparisons. It's harder than it looks in the presence of universal equality and subtyping.

% scalac3 ./a.scala 
./a.scala:20: warning: Test and Foo are unrelated: they will most likely never compare equal
    if (t==f)
         ^
one warning found

I am looking for more strict error checking.

The recent (May 2016) post " Multiversal Equality for Scala ", written by the creator of Scala himself Martin Odersky , is trying to address that.

The current status in Scala is that the compiler will give warnings for some comparisons that are always false. But the coverage is weak. For instance this will give a warning:

scala> 1 == "abc"
<console>:12: warning: comparing values of types Int and String using `==' will always yield false

But this will not:

scala> "abc" == 1
res2: Boolean = false

I believe to do better, we need to enlist the cooperation of developers.
Ultimately it's the developer who provides implementations of equality methods and who is therefore best placed to characterize which equalities make sense.

The best known way to characterize such relationships is with type classes.
Implicit values of a trait Eq[T, U] can capture the property that values of type T can be compared to values of type U .
Here's the definition of Eq :

package scala
trait Eq[-T, -U]

Given a set of Eq instances, the idea is that the Scala compiler will check every time it encounters a potentially problematic comparison between values of types T and U that there is an implicit instance of Eq[T, U] .
A comparison is potentially problematic if it is between incompatible types.
As long as T <: U or U <: T the equality could make sense because both sides can potentially be the same value.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM