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

org.scalautils.ConversionCheckedTripleEquals.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.scalautils

import TripleEqualsSupport._

/**
 * Provides === and !== operators that return Boolean, delegate the equality determination
 * to an Equality type class, and require that either the types of the two values compared are in a subtype/supertype
 * relationship, or that an implicit conversion is available that can convert from one type to the other.
 * 
 * 
* Recommended Usage: * Trait ConversionCheckedTripleEquals is useful (in both production and test code) when you need determine equality for a type of object differently than * its equals * method—either you can't change the equals method, or the equals method is sensible generally, but you're in a special situation where you * need something else—and/or you want a compile-time type check that allows types that are implicitly convertable in either (or both) directions. *
* *

* This trait is the middle ground of the three triple equals traits, in between * TripleEquals, the most lenient, and TypeCheckedTripleEquals, the most strict. * If TripleEquals is mixed in or imported, the === can be used with any two types * and still compile. If TypeCheckedTripleEquals is mixed in or imported, however, only types in * a subtype or supertype relationship with each other (including when both types are exactly the same) will compile. * ConversionCheckedTripleEquals is slightly more accomodating, because in addition to compiling any * use of === that will compile under TypeCheckedTripleEquals, it will also compile * type types that would be rejected by TypeCheckedTripleEquals, so long as an implicit * conversion (in either direction) from one type to another is available. *

* *

* For example, under TypeCheckedTripleEquals, the following use of === will not compile, * because Int and Long are not in a subtype/supertype relationship. (I.e., Int * is not a subtype or supertype of Long): *

* *
 * scala> import org.scalautils._
 * import org.scalautils._
 * 
 * scala> import TypeCheckedTripleEquals._
 * import TypeCheckedTripleEquals._
 * 
 * scala> 1 === 1L
 * <console>:14: error: types Int and Long do not adhere to the equality constraint selected for
 * the === and !== operators; they must either be in a subtype/supertype relationship, or, if
 * ConversionCheckedTripleEquals is in force, implicitly convertible in one direction or the other;
 * the missing implicit parameter is of type org.scalautils.Constraint[Int,Long]
 *               1 === 1L
 *                 ^
 * 
* *

* Trait TypeCheckedTripleEquals rejects types Int and Long because they are not directly related via * subtyping. However, an implicit widening conversion from Int to Long does exist (imported implicitly from * scala.Predef), so ConversionCheckedTripleEquals * will allow it: *

* *
 * scala> import ConversionCheckedTripleEquals._
 * import ConversionCheckedTripleEquals._
 * 
 * scala> 1 === 1L
 * res1: Boolean = true
 * 
* *

* The implicit conversion can go in either direction: from the left type to the right type, or vice versa. In the above expression the * implicit conversion goes from left to right (the Int on the left to the Long on the right). It also works * the other way: *

* *
 * scala> 1L === 1
 * res2: Boolean = true
 * 
* *

* This trait will override or hide implicit methods defined by its sibling traits, * TripleEquals or TypeCheckedTripleEquals, * and can therefore be used to temporarily turn on or off conversion checking in a limited scope. Here's an example, in which TypeCheckedTripleEquals will * cause a compiler error: *

* *
 * import org.scalautils._
 * import TypeCheckedTripleEquals._
 *
 * object Example {
 *
 *   def cmp(a: Int, b: Long): Int = {
 *     if (a === b) 0       // This line won't compile
 *     else if (a < b) -1
 *     else 1
 *   }
 *
 *  def cmp(s: String, t: String): Int = {
 *    if (s === t) 0
 *    else if (s < t) -1
 *    else 1
 *  }
 * }
 * 
* *

* Because Int and Long are not in a subtype/supertype relationship, comparing 1 and 1L in the context * of TypeCheckedTripleEquals will generate a compiler error: *

* *
 * Example.scala:9: error: types Int and Long do not adhere to the equality constraint selected for
 * the === and !== operators; they must either be in a subtype/supertype relationship, or, if
 * ConversionCheckedTripleEquals is in force, implicitly convertible in one direction or the other;
 * the missing implicit parameter is of type org.scalautils.Constraint[Int,Long]
 *     if (a === b) 0      // This line won't compile
 *           ^
 * one error found
 * 
* *

* You can “relax” the type checking (i.e., by additionally allowing implicitly convertible types) locally by importing * the members of ConversionCheckedTripleEquals in a limited scope: *

* *
 * package org.scalautils.examples.conversioncheckedtripleequals
 * 
 * import org.scalautils._
 * import TypeCheckedTripleEquals._
 * 
 * object Example {
 * 
 *   def cmp(a: Int, b: Long): Int = {
 *     import ConversionCheckedTripleEquals._
 *     if (a === b) 0
 *     else if (a < b) -1
 *     else 1
 *   }
 *
 *  def cmp(s: String, t: String): Int = {
 *    if (s === t) 0
 *    else if (s < t) -1
 *    else 1
 *  }
 * }
 * 
* *

* With the above change, the Example.scala file compiles fine. Conversion checking is enabled only inside the first cmp method that * takes an Int and a Long. TypeCheckedTripleEquals is still enforcing its type constraint, for example, for the s === t * expression in the other overloaded cmp method that takes strings. *

* *

* Because the methods in ConversionCheckedTripleEquals (and its siblings) * override all the methods defined in supertype TripleEqualsSupport, you can achieve the same * kind of nested tuning of equality constraints whether you mix in traits, import from companion objects, or use some combination of both. *

* *

* In short, you should be able to select a primary constraint level via either a mixin or import, then change that in nested scopes * however you want, again either through a mixin or import, without getting any implicit conversion ambiguity. The innermost constraint level in scope * will always be in force. *

* *

* An alternative way to solve an unwanted compiler error caused by an over-zealous type constraint is with a widening type ascription. Here * are some examples: *

* *
 * scala> import org.scalautils._
 * import org.scalautils._
 *
 * scala> import ConversionCheckedTripleEquals._
 * import ConversionCheckedTripleEquals._
 *
 * scala> List(1, 2, 3) === Vector(1, 2, 3)
 * <console>:14: error: types List[Int] and scala.collection.immutable.Vector[Int] do not adhere to the equality constraint selected for the === and !== operators; the missing implicit parameter is of type org.scalautils.Constraint[List[Int],scala.collection.immutable.Vector[Int]]
 *               List(1, 2, 3) === Vector(1, 2, 3)
 *                             ^
 * 
* *

* Although you could solve the above type error with TraversableEqualityConstraints, you could also * simply widen the type of one side or the other to Any. Because Any is a supertype of everything, the * type constraint will be satisfied: *

* *
 * scala> List(1, 2, 3) === (Vector(1, 2, 3): Any)
 * res1: Boolean = true
 *
 * scala> (List(1, 2, 3): Any) === Vector(1, 2, 3)
 * res2: Boolean = true
 * 
* *

* You could alternatively widen a type to a more specific common supertype than Any. For example, since List[Int] and * Vector[Int] are both subtypes of Seq[Int], so you could widen either type to Seq[Int] to satisfy * the type checker: *

* *
 * scala> List(1, 2, 3) === (Vector(1, 2, 3): Seq[Int])
 * res3: Boolean = true
 *
 * scala> (List(1, 2, 3): Seq[Int]) === Vector(1, 2, 3)
 * res4: Boolean = true
 * 
* * @author Bill Venners */ trait ConversionCheckedTripleEquals extends LowPriorityConversionCheckedConstraint { import scala.language.implicitConversions // Inherit the Scaladoc for these methods override def convertToEqualizer[T](left: T): Equalizer[T] = new Equalizer(left) implicit override def convertToCheckingEqualizer[T](left: T): CheckingEqualizer[T] = new CheckingEqualizer(left) override def convertToLegacyEqualizer[T](left: T): LegacyEqualizer[T] = new LegacyEqualizer(left) override def convertToLegacyCheckingEqualizer[T](left: T): LegacyCheckingEqualizer[T] = new LegacyCheckingEqualizer(left) override def unconstrainedEquality[A, B](implicit equalityOfA: Equality[A]): Constraint[A, B] = new EqualityConstraint[A, B](equalityOfA) override def lowPriorityTypeCheckedConstraint[A, B](implicit equivalenceOfB: Equivalence[B], ev: A <:< B): Constraint[A, B] = new AToBEquivalenceConstraint[A, B](equivalenceOfB, ev) override def convertEquivalenceToAToBConstraint[A, B](equivalenceOfB: Equivalence[B])(implicit ev: A <:< B): Constraint[A, B] = new AToBEquivalenceConstraint[A, B](equivalenceOfB, ev) override def typeCheckedConstraint[A, B](implicit equivalenceOfA: Equivalence[A], ev: B <:< A): Constraint[A, B] = new BToAEquivalenceConstraint[A, B](equivalenceOfA, ev) override def convertEquivalenceToBToAConstraint[A, B](equivalenceOfA: Equivalence[A])(implicit ev: B <:< A): Constraint[A, B] = new BToAEquivalenceConstraint[A, B](equivalenceOfA, ev) implicit override def conversionCheckedConstraint[A, B](implicit equivalenceOfA: Equivalence[A], cnv: B => A): Constraint[A, B] = new BToAEquivalenceConstraint[A, B](equivalenceOfA, cnv) implicit override def convertEquivalenceToBToAConversionConstraint[A, B](equivalenceOfA: Equivalence[A])(implicit ev: B => A): Constraint[A, B] = new BToAEquivalenceConstraint[A, B](equivalenceOfA, ev) } /** * Companion object to trait ConversionCheckedTripleEquals that facilitates the importing of ConversionCheckedTripleEquals members as * an alternative to mixing it in. One use case is to import ConversionCheckedTripleEquals members so you can use * them in the Scala interpreter: * *
 * $ scala -classpath scalautils.jar
 * Welcome to Scala version 2.10.0
 * Type in expressions to have them evaluated.
 * Type :help for more information.
 *
 * scala> import org.scalautils._
 * import org.scalautils._
 * 
 * scala> import ConversionCheckedTripleEquals._
 * import ConversionCheckedTripleEquals._
 * 
 * scala> 1 === 1L
 * res0: Boolean = true
 * 
*/ object ConversionCheckedTripleEquals extends ConversionCheckedTripleEquals




© 2015 - 2024 Weber Informatics LLC | Privacy Policy