All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalactic.Equality.scala Maven / Gradle / Ivy

The newest version!
/*
 * 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 Doubles 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 Equivalences 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 Equivalences instead of Equalitys. * If you do chose to write the more general Equalitys, 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] }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy