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 Double
s 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 Double
s.
*
*
*
* 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, Uniformity
s allow you to the Explicitly
DSL with
* TripleEquals
, whereas Normalization
s require
* TypeCheckedTripleEquals
.
* Uniformity
s 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 String
s (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
*/
infix
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)
}
}