org.scalactic.Equality.scala Maven / Gradle / Ivy
/*
* Copyright 2001-2013 Artima, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.scalactic
/**
* 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.
*
*
* @tparam A the type whose equality is being customized
*/
trait Equality[A] extends Equivalence[A] {
/*
*
* The equals
method of java.lang.Object
and areEqual
method of trait Equality
have a similar
* signatures and behavior, and you write them in a similar way.
* When using TypeCheckedTripleEquals
or
* ConversionCheckedTripleEquals
, however,
*
*
*/
/**
* Indicates whether the objects passed as a
and b
are equal.
*
* @param a a left-hand value being compared with another (right-hand-side one) for equality (e.g., a == b
)
* @param b a right-hand value being compared with another (left-hand-side one) for equality (e.g., a == b
)
* @return true if the passed objects are "equal," as defined by this Equality
instance
*/
def areEqual(a: A, b: Any): Boolean
/**
* A final implementation of the areEquivalent
method of Equivalence
that just passes
* a
and b
to areEqual
and returns the result.
*
*
* This method enables any Equality
to be used where an Equivalence
is needed, such
* as the implicit enabling methods of TypeCheckedTripleEquals
* and ConversionCheckedTripleEquals
.
*
*
* @param a a left-hand value being compared with another, right-hand, value for equality (e.g., a == b
)
* @param b a right-hand value being compared with another, left-hand, value for equality (e.g., a == b
)
* @return true if the passed objects are "equal," as defined by the areEqual
method of this
* Equality
instance
*/
final def areEquivalent(a: A, b: A): Boolean = areEqual(a, b)
}
/**
* Companion object for trait Equality
that provides factory methods for producing Equality
* instances.
*/
object Equality {
/**
* Produces a NormalizingEquality[A]
whose normalized
,
* normalizedCanHandle
, and normalizedOrSame
methods delegate
* to the passed Uniformity[A]
.
*
* @tparam A the type of passed Uniformity
and returned NormalizingEquality
.
* @param uniformity the Uniformity
to which the returned NormalizingEquality
* should delegate.
*/
def apply[A](uniformity: Uniformity[A]): NormalizingEquality[A] = {
new NormalizingEquality[A] {
def normalized(a: A): A = uniformity.normalized(a)
def normalizedCanHandle(b: Any): Boolean = uniformity.normalizedCanHandle(b)
def normalizedOrSame(b: Any): Any = uniformity.normalizedOrSame(b)
}
}
/**
* Provides default Equality
implementations for the specified type whose
* areEqual
method first calls .deep
on any Array
(on either the left or right side),
* then compares the resulting objects with ==
.
*
* @return a default Equivalence[A]
*/
implicit def default[A]: Equality[A] = new DefaultEquality[A]
}