org.scalactic.Uniformity.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 normalize instances of a type that can also handle normalization of that type when passed as Any.
*
*
* For example, to normalize Doubles by truncating off any decimal part,
* you might write:
*
*
*
* import org.scalactic._
*
* val truncated =
* new Uniformity[Double] {
* def normalized(d: Double) = d.floor
* def normalizedCanHandle(o: Any) = o.isInstanceOf[Double]
* def normalizedOrSame(o: Any): Any =
* o match {
* case d: Double => normalized(d)
* case _ => o
* }
* }
*
*
*
* Given this definition you could use it with the Explicitly DSL like this:
*
*
*
* import org.scalatest._
* import Matchers._
*
* 2.1 should equal (2.0) (after being truncated)
*
*
*
* If you make the truncated val implicit and import or mix in the members of NormMethods,
* you can access the behavior by invoking .norm on Doubles.
*
*
*
* implicit val doubleUniformity = truncated
* import NormMethods._
*
* val d = 2.1
* d.norm // returns 2.0
*
*
*
* Note that by creating a Uniformity rather than just an instance of its supertype, Normalization,
* it can be used more generally. For example, Uniformitys allow you to the Explicitly DSL with
* TripleEquals, whereas Normalizations require
* TypeCheckedTripleEquals.
* Uniformitys also enable you to use the Explicitly DSL with ScalaTest's should ===, equal,
* and contain matcher syntax, whereas a plain Normalization can only be used with should ===, and only
* under TypeCheckedTripleEquals.
*
*
* @tparam A the type whose uniformity is being defined
*/
trait Uniformity[A] extends Normalization[A] { thisUniformity =>
/**
* Returns either the result of passing this object to normalized, if appropriate, or the same object.
*
*
* Implementations can decide what “appropriate” means, but the intent is that it will usually mean the
* value passed is of the type A. For example, if this is a Uniformity[String], appropriate means
* that the value (of type Any) passed is actually an instance of String. Because of erasure,
* however, a Uniformity[List[String]] will only be able to tell whether a value is a List[_],
* so it might declare any List[_] that contains only Strings (determined by invoking
* isInstanceOf[String] on each element) to be appropriate. This means a Uniformity[List[String]] might normalize
* a List[AnyRef] that happens to contain only Strings.
*
*
* @param b the object to normalize, if appropriate
* @return a normalized form of the passed object, if this Uniformity was able to normalize it, else the same object passed
*/
def normalizedOrSame(b: Any): Any
/**
* Indicates whether this Uniformity's normalized method can “handle” the passed object, if cast to the
* appropriate type A.
*
*
* If this method returns true for a particular passed object, it means that if the object is passed
* to normalizedOrSame, that method will return the result of passing it to normalized.
* It does not mean that the object will necessarily be modified when passed to normalizedOrSame or normalized.
* For example, the lowerCased field of StringNormalizations is a Uniformity[String]
* that normalizes strings by forcing all characters to lower case:
*
*
*
* scala> import org.scalactic._
* import org.scalactic._
*
* scala> import StringNormalizations._
* import StringNormalizations._
*
* scala> lowerCased
* res0: org.scalactic.Uniformity[String] = lowerCased
*
* scala> lowerCased.normalized("HALLOOO!")
* res1: String = hallooo!
*
*
*
* Now consider two strings held from variables of type AnyRef:
*
*
*
* scala> val yell: AnyRef = "HOWDY"
* yell: AnyRef = HOWDY
*
* scala> val whisper: AnyRef = "howdy"
* whisper: AnyRef = howdy
*
*
*
* As you would expect, when yell is passed to normalizedCanHandle, it returns true, and when
* yell is passed to normalizedOrSame, it returns a lower-cased (normal) form:
*
*
*
* scala> lowerCased.normalizedCanHandle(yell)
* res2: Boolean = true
*
* scala> lowerCased.normalizedOrSame(yell)
* res3: Any = howdy
*
*
*
* A similar thing happens, however, when whisper is passed to normalizedCanHandle and normalizedOrSame,
* even though in this case the string is already in normal form according to the lowerCased Uniformity:
*
*
*
* scala> lowerCased.normalizedCanHandle(whisper)
* res4: Boolean = true
*
* scala> lowerCased.normalizedOrSame(whisper)
* res5: Any = howdy
*
*
*
* This illustrates that normalizedCanHandle does not indicate that the passed object is not in normalized form already, just that
* it can be be handled by the normalized method. This further means that the normalized method itself
* simply ensures that objects are returned in normal form. It need not necessarily change them: if a passed object is already in
* normal form, normalized can (and usually should) return the exact same object. That is in fact what happened when we normalized
* whisper. Since whisper's value of "hello" was already in normal form (all lower-cased), normalized (
* invoked by the normalizedOrSame method) returned the exact same object passed:
*
*
*
* scala> val whisperNormed = res5.asInstanceOf[AnyRef]
* whisperNormed: AnyRef = howdy
*
* scala> whisperNormed eq whisper
* res8: Boolean = true
*
*/
def normalizedCanHandle(b: Any): Boolean
/**
* Returns a new Uniformity that combines this and the passed Uniformity.
*
*
* The normalized and normalizedOrSame methods
* of the Uniformity returned by this method return a result
* obtained by forwarding the passed value first to this Uniformity's implementation of the method,
* then passing that result to the other Uniformity's implementation of the method, respectively.
* Essentially, the body of the composed normalized method is:
*
*
*
* uniformityPassedToAnd.normalized(uniformityOnWhichAndWasInvoked.normalized(a))
*
*
*
* And the body of the composed normalizedOrSame method is:
*
*
*
* uniformityPassedToAnd.normalizedOrSame(uniformityOnWhichAndWasInvoked.normalizedOrSame(a))
*
*
*
* The normalizeCanHandle method of the Uniformity returned by this method returns a result
* obtained by anding the result of forwarding the passed value to this Uniformity's implementation of the method
* with the result of forwarding it to the passed Uniformity's implementation.
* Essentially, the body of the composed normalizeCanHandle method is:
*
*
*
* uniformityOnWhichAndWasInvoked.normalizeCanHandle(a) && uniformityPassedToAnd.normalizeCanHandle(a)
*
*
* @param other a Uniformity to 'and' with this one
* @return a Uniformity representing the composition of this and the passed Uniformity
*/
final def and(other: Uniformity[A]): Uniformity[A] =
new Uniformity[A] {
// Note in Scaladoc what order, and recommend people don't do side effects anyway.
// By order, I mean left's normalized gets called first then right's normalized gets called on that result, for "left and right"
def normalized(a: A): A = other.normalized(thisUniformity.normalized(a))
def normalizedCanHandle(b: Any): Boolean = other.normalizedCanHandle(b) && thisUniformity.normalizedCanHandle(b)
def normalizedOrSame(b: Any): Any = other.normalizedOrSame(thisUniformity.normalizedOrSame(b))
}
/**
* Converts this Uniformity to a NormalizingEquality[A] whose normalized,
* normalizedCanHandle, and normalizedOrSame methods delegate
* to this Uniformity[A] and whose afterNormalizationEquality field returns the
* implicitly passed Equality[A].
*
* @param equality the Equality that the returned NormalizingEquality
* will delegate to determine equality after normalizing both left and right (if appropriate) sides.
*/
final def toEquality(implicit equality: Equality[A]): NormalizingEquality[A] =
new NormalizingEquality[A] {
override val afterNormalizationEquality = equality
def normalized(a: A): A = thisUniformity.normalized(a)
def normalizedCanHandle(b: Any): Boolean = thisUniformity.normalizedCanHandle(b)
def normalizedOrSame(b: Any): Any = thisUniformity.normalizedOrSame(b)
}
}