trait Equality[A] extends Equivalence[A]
Defines a custom way to determine equality for a type when compared with another value of type Any
.
Equality
enables you to define alternate notions of equality for types that can be used
with ScalaUtil's ===
and !==
syntax and ScalaTest's matcher syntax.
For example, say you have a case class that includes a Double
value:
scala> case class Person(name: String, age: Double) defined class Person
Imagine you are calculating the age
values in such as way that occasionally tests
are failing because of rounding differences that you actually don't care about. For example, you
expect an age of 29.0, but you're sometimes seeing 29.0001:
scala> import org.scalactic._ import org.scalactic._ scala> import TripleEquals._ import TripleEquals._ scala> Person("Joe", 29.0001) === Person("Joe", 29.0) res0: Boolean = false
The ===
operator looks for an implicit Equality[L]
, where L
is the left-hand type: in this
case, Person
. Because you didn't specifically provide an implicit Equality[Person]
, ===
will fall back on
default equality, which will call Person
's equals
method. That equals
method, provided by the Scala compiler
because Person
is a case class, will declare these two objects unequal because 29.001 does not exactly equal 29.0.
To make the equality check more forgiving, you could define an implicit Equality[Person]
that compares
the age
Double
s with a tolerance, like this:
scala> import Tolerance._ import Tolerance._ scala> implicit val personEq = | new Equality[Person] { | def areEqual(a: Person, b: Any): Boolean = | b match { | case p: Person => a.name == p.name && a.age === p.age +- 0.0002 | case _ => false | } | } personEq: org.scalactic.Equality[Person] = $anon$1@2b29f6e7
Now the ===
operator will use your more forgiving Equality[Person]
for the equality check instead
of default equality:
scala> Person("Joe", 29.0001) === Person("Joe", 29.0) res1: Boolean = true
Default equality
Scalactic defines a default Equality[T]
for all types T
whose areEqual
method works by first
calling .deep
on any passed array, then calling ==
on the left-hand object, passing in the right-hand object.
You can obtain a default equality via the default
method of the Equality companion object,
or from the defaultEquality
method defined in TripleEqualsSupport
.
About equality and equivalence
The Equality
trait represents the Java Platform's native notion of equality, as expressed in the signature and contract of
the equals
method of java.lang.Object
. Essentially, trait Equality
enables you to write alternate
equals
method implementations for a type outside its defining class.
In an equals
method, the left-hand type is known to be the type of this
, but
the right-hand type is Any
.
As a result, you would normally perform a runtime type test to determine whether the right-hand object is of an appropriate type for equality,
and if so, compare it structurally for equality with the left-hand (this
) object.
An an illustration, here's a possible equals
implementation for the Person
case class shown in the earlier example:
override def equals(other: Any): Boolean = other match { case p: Person => name = p.name && age = p.age case _ => false }
The areEquals
method of Equality[T]
is similar. The left-hand type is known to be T
, but the right-hand type is Any
, so
normally you'd need to do a runtime type test in your areEqual
implementation.
Here's the areEqual
method implementation from the earlier Equality[Person]
example:
def areEqual(a: Person, b: Any): Boolean = b match { case p: Person => a.name == p.name && a.age === p.age +- 0.0002 case _ => false }
Equality
is used by TripleEquals
, which enforces no type constraint between the left and right values, and the
equal
, be
, and contain
syntax of ScalaTest Matchers.
By contrast, TypeCheckedTripleEquals
and ConversionCheckedTripleEquals
use an Equivalence
.
Equivalence
differs from Equality
in that both the left and right values are of the same type. Equivalence
works for
TypeCheckedTripleEquals
because the type constraint enforces that the left type is a subtype or supertype of (or the same type as) the right
type, and it widens the subtype to the supertype. So ultimately, both left and right sides are of the supertype type. Similarly, Equivalence
works for ConversionCheckedTripleEquals
because the type constraint enforces that an implicit conversion
exists from either the left type to the right type, or the right type to the left type, and it always converts one
type to the other using the implicit conversion. (If both types are the same type, the identity implicit conversion
from Predef
is used.) Because of the conversion, both left and right sides are ultimately of the
converted-to type. Here's an example of how writing an Equivalence
's areEquivalent
method might look:
def areEquivalent(a: Person, b: Person): Boolean = a.name == b.name && a.age === b.age +- 0.0002
Scalactic provides both Equality
and Equivalence
because the Any
in
Equality
can sometimes make things painful. For example, in trait
TolerantNumerics
,
a single generic factory method can produce Equivalence
s for any Numeric
type,
but because of the Any
, a separate factory method must be defined to produce an Equality
for each Numeric
type.
If you just want to customize the notion of equality for ===
used in Boolean
expressions, you can work with Equivalence
s instead of Equality
s.
If you do chose to write the more general Equality
s, they can be used wherever an Equivalence
is required, because Equality
extends Equivalence
, defining a final implementation of
areEquivalent
that invokes areEqual
.
Note: The Equality
type class was inspired in part by the Equal
type class of the
scalaz
project.
- A
the type whose equality is being customized
- Source
- Equality.scala
- Alphabetic
- By Inheritance
- Equality
- Equivalence
- AnyRef
- Any
- Hide All
- Show All
- Public
- Protected
Abstract Value Members
- abstract def areEqual(a: A, b: Any): Boolean
Indicates whether the objects passed as
a
andb
are equal.Indicates whether the objects passed as
a
andb
are equal.- a
a left-hand value being compared with another (right-hand-side one) for equality (e.g.,
a == b
)- b
a right-hand value being compared with another (left-hand-side one) for equality (e.g.,
a == b
)- returns
true if the passed objects are "equal," as defined by this
Equality
instance
Concrete Value Members
- final def !=(arg0: Any): Boolean
- Definition Classes
- AnyRef → Any
- final def ##: Int
- Definition Classes
- AnyRef → Any
- final def ==(arg0: Any): Boolean
- Definition Classes
- AnyRef → Any
- final def areEquivalent(a: A, b: A): Boolean
A final implementation of the
areEquivalent
method ofEquivalence
that just passesa
andb
toareEqual
and returns the result.A final implementation of the
areEquivalent
method ofEquivalence
that just passesa
andb
toareEqual
and returns the result.This method enables any
Equality
to be used where anEquivalence
is needed, such as the implicit enabling methods ofTypeCheckedTripleEquals
andConversionCheckedTripleEquals
.- a
a left-hand value being compared with another, right-hand, value for equality (e.g.,
a == b
)- b
a right-hand value being compared with another, left-hand, value for equality (e.g.,
a == b
)- returns
true if the passed objects are "equal," as defined by the
areEqual
method of thisEquality
instance
- Definition Classes
- Equality → Equivalence
- final def asInstanceOf[T0]: T0
- Definition Classes
- Any
- def clone(): AnyRef
- Attributes
- protected[lang]
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.CloneNotSupportedException]) @native()
- final def eq(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef
- def equals(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef → Any
- def finalize(): Unit
- Attributes
- protected[lang]
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.Throwable])
- final def getClass(): Class[_ <: AnyRef]
- Definition Classes
- AnyRef → Any
- Annotations
- @native()
- def hashCode(): Int
- Definition Classes
- AnyRef → Any
- Annotations
- @native()
- final def isInstanceOf[T0]: Boolean
- Definition Classes
- Any
- final def ne(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef
- final def notify(): Unit
- Definition Classes
- AnyRef
- Annotations
- @native()
- final def notifyAll(): Unit
- Definition Classes
- AnyRef
- Annotations
- @native()
- final def synchronized[T0](arg0: => T0): T0
- Definition Classes
- AnyRef
- def toString(): String
- Definition Classes
- AnyRef → Any
- final def wait(): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException])
- final def wait(arg0: Long, arg1: Int): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException])
- final def wait(arg0: Long): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException]) @native()