Scalactic User Guide

Custom equality

Default equality

Constrained equality

Tolerance

Normalization

The Explicitly DSL

Or and Every

Requirements

Snapshots

TimesOnInt

Constrained equality

Scalactic provides a way to get a compile-time type error if two types being compared with === violate a tunable type Constraint. The basic TripleEquals trait allows you to compare any type for equality with any other type:

scala> import org.scalactic._
import org.scalactic._

scala> import TripleEquals._
import TripleEquals._

scala> Some(1) === 2
res0: Boolean = false

scala> 1 === 1L
res1: Boolean = true

TypeCheckedTripleEquals will compile only if the left and right sides are in a subtype/supertype relationship (including when the left and right sides are exactly the same type):

scala> import TypeCheckedTripleEquals._
import TypeCheckedTripleEquals._

scala> Some(1) === 2
<console>:17: error: types Some[Int] and Int do not adhere to the type constraint selected for the === and !== operators;
    the missing implicit parameter is of type org.scalactic.Constraint[Some[Int],Int]
              Some(1) === 2

scala> 1 === 1L
<console>:17: error: types Int and Long do not adhere to the type constraint selected for the === and !== operators;
    the missing implicit parameter is of type org.scalactic.Constraint[Int,Long]
              1 === 1L
                ^

Note that the latter case is an example of an over-protective type error. If this expression were allowed to compile and run, it would yield true, because the Int would be implicitly converted to Long. One way to solve such errors is with a type ascription. In this case, you can case either side to AnyVal, a supertype of both Int and Long:

scala> 1 === (1L: AnyVal)
res7: Boolean = true

scala> 1L === (1: AnyVal)
res8: Boolean = true

Since every type is a subtype of Any, you can always solve an over-protective type error with a type ascription of Any.

Another way to address this type error, since an implicit conversion exists between the two types, is to use ConversionCheckedTripleEquals. ConversionCheckedTripleEquals will compile if the left and right sides are in a subtype/supertype relationship or an implicit conversion exists between the left and right sides:

scala> import ConversionCheckedTripleEquals._
import ConversionCheckedTripleEquals._

scala> Some(1) === 2
<console>:20: error: types Some[Int] and Int do not adhere to the type constraint selected for the === and !== operators;
    the missing implicit parameter is of type org.scalactic.Constraint[Some[Int],Int]
              Some(1) === 2
                      ^

scala> 1 === 1L
res5: Boolean = true

The comparison between Int and Long in the previous ConversionCheckedTripleEquals example compiles because an implicit widening conversion from Int to Long is defined in scala.Predef. The implicit conversion can be in either direction. In the previous example, it is from the left type (Int) to the right type (Long). Here the implicit conversion is from the right type (Int) to the left type (Long):

scala> 1L === 1
res6: Boolean = true

Another way to solve over-protective type errors is to actually supply Constraint objects that will allow specific types causing the errors to compile. For example, Scalactic provides Constraints for Scala's collection types, whose equality behavior is not modeled by either TypeCheckedTripleEquals or ConversionCheckedTripleEquals. If the following were allowed to compile, it would yield true, because two Seqs are considered equal if they contain equal elements in the same order:

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 type constraint
    selected for the === and !== operators; the missing implicit parameter is of type
    org.scalactic.Constraint[List[Int],scala.collection.immutable.Vector[Int]]
              List(1, 2, 3) === Vector(1, 2, 3)
                            ^

Trait TraversableEqualityConstraints provides type constraints that allow Seqs to be compared with Seqs, Sets with Sets, and Maps with Maps, so long as an equality constraint exists between the respective element types:

scala> import TraversableEqualityConstraints._
import TraversableEqualityConstraints._

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

scala> List(1, 2, 3) === Vector("1", "2", "3")
<console>:17: error: types List[Int] and scala.collection.immutable.Vector[String] do not adhere to the type constraint
    selected for the === and !== operators; the missing implicit parameter is of type 
    org.scalactic.Constraint[List[Int],scala.collection.immutable.Vector[String]]
              List(1, 2, 3) === Vector("1", "2", "3")
                            ^

The last example doesn't compile because no equality constraint exists between the element types, Int and String. Most often the constraints provided by Scalactic will be all you need, but if you do find yourself struggling with over-protective type errors, you can look at TraversableEqualityConstraints for examples of how to provide Constraints for your own types.

Next, learn about Tolerance.

Scalactic is brought to you by Bill Venners, with contributions from several other folks. It is sponsored by Artima, Inc.
ScalaTest is free, open-source software released under the Apache 2.0 license.

Copyright © 2009-2024 Artima, Inc. All Rights Reserved.

artima