Last active
August 29, 2015 14:04
-
-
Save bvenners/eb746eb23af8f8373d74 to your computer and use it in GitHub Desktop.
Types involved in ScalaTest matcher expressions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Most ScalaTest matcher syntax is enabled by the existence of typeclasses. For example, | |
// 'have length 3' will work for any type T for which a Length[T] instead is available. | |
// The result type of 'have length 3' is a MatcherFactory1[Any, Length]. | |
// | |
scala> val haveLength3 = have length 3 | |
haveLength3: org.scalatest.matchers.MatcherFactory1[Any,org.scalatest.enablers.Length] = have length 3 | |
// | |
// A MatcherFactory1[Any, Length]'s 'matcher' method can produce a Matcher[T] for any | |
// type T that is a subtype of Any, so long as a Length[T] is available. | |
// | |
scala> haveLength3.matcher[String] | |
res56: org.scalatest.matchers.Matcher[String] = have length 3 | |
scala> haveLength3.matcher[List[Int]] | |
res57: org.scalatest.matchers.Matcher[List[Int]] = have length 3 | |
scala> haveLength3.matcher[java.util.Date] | |
<console>:21: error: could not find implicit value for evidence parameter | |
of type org.scalatest.enablers.Length[java.util.Date] | |
haveLength3.matcher[java.util.Date] | |
^ | |
// | |
// In master the 'equal' matcher now returns a MatcherFactory1[Any, EvidenceThat[R]#CanEqual], | |
// where R is the type of value passed to equal. Below a Vector[String] is passed to equal, | |
// so the result type is MatcherFactory1[Any, EvidenceThat[Vector[String]]#CanEqual]: | |
// | |
scala> val notEqualVectorOfString = not equal Vector("one", "two", "three") | |
notEqualVectorOfString: org.scalatest.matchers.MatcherFactory1[Any, | |
org.scalactic.enablers.EvidenceThat[scala.collection.immutable.Vector[String]]#CanEqual] = not (equal (Vector("one", "two", "three"))) | |
// | |
// So like before, this matcher factory can produce a Matcher[T] for any type T (because the | |
// first type parameter is the upper bound, in this case, Any] given an instance of | |
// a EvidenceThat[Vector[String]]#CanEqual[T]. Here are some examples: | |
// | |
scala> notEqualVectorOfString.matcher[List[Int]] | |
<console>:21: error: could not find implicit value for evidence parameter of | |
type org.scalactic.enablers.EvidenceThat[scala.collection.immutable.Vector[String]]#CanEqual[List[Int]] | |
notEqualVectorOfString.matcher[List[Int]] | |
^ | |
scala> notEqualVectorOfString.matcher[List[String]] | |
res60: org.scalatest.matchers.Matcher[List[String]] = not (equal (Vector("one", "two", "three"))) | |
scala> notEqualVectorOfString.matcher[Set[String]] | |
<console>:21: error: could not find implicit value for evidence parameter of type | |
org.scalactic.enablers.EvidenceThat[scala.collection.immutable.Vector[String]]#CanEqual[Set[String]] | |
notEqualVectorOfString.matcher[Set[String]] | |
^ | |
// | |
// Matcher factories can be composed with the and, or, and not combinators, just like plain old | |
// matchers. If you combine two matcher factories with 'and' or 'or', you'll get a new matcher factory | |
// with the least upper bound of the two composed upper bounds for its upper bound, and the union | |
// of the required type classes. Matcher factories are essentially type-class type-constructor | |
// recorders. For example, if you 'and' together haveLength3 and notEqualVectorOfString, you'll get a | |
// MatcherFactory2[Any, Length, EvidenceThat[Vector[String]]#CanEqual]: | |
// | |
scala> val anotherFactory = haveLength3 and notEqualVectorOfString | |
anotherFactory: org.scalatest.matchers.MatcherFactory2[Any,org.scalatest.enablers.Length, | |
org.scalactic.enablers.EvidenceThat[scala.collection.immutable.Vector[String]]#CanEqual] = org.scalatest.matchers.MatcherFactory1$$anon$27@46001ea4 | |
// | |
// This matcher factory's matcher method can produce a matcher for any type T that | |
// is a subtype of the upper bound, Any, and for which a type class instance for both | |
// Length[T] and EvidenceThat[Vector[String]]#CanEqual[T] is available. These are both | |
// available, for example, for type List[String]: | |
// | |
scala> anotherFactory.matcher[List[String]] | |
res63: org.scalatest.matchers.Matcher[List[String]] = <function1> | |
// | |
// But although a Length[List[Int]] does exist, there is no implicit instance of | |
// EvidenceThat[Vector[String]]#CanEqual[List[Int]], so you get a type error: | |
// | |
scala> anotherFactory.matcher[List[Int]] | |
<console>:23: error: could not find implicit value for evidence parameter of type | |
org.scalactic.enablers.EvidenceThat[scala.collection.immutable.Vector[String]]#CanEqual[List[Int]] | |
anotherFactory.matcher[List[Int]] | |
^ | |
// | |
// When you pass a matcher factory to should, it tries to use that factory to | |
// produce a Matcher[L], where L is the left-hand type on which 'should' was | |
// invoked. So when the left-hand type is List[String], the should method will | |
// try and obtain a Matcher[List[String]] from the matcher factory. This works | |
// just fine: | |
// | |
scala> List("one", "two", "three") should anotherFactory | |
org.scalatest.exceptions.TestFailedException: List("one", "two", "three") had length 3, | |
but List("one", "two", "three") equaled Vector("one", "two", "three") | |
// | |
// However, it doesn't compile if the left-hand type is List[Int]: | |
// | |
scala> List(1, 2, 3) should anotherFactory | |
<console>:23: error: could not find implicit value for parameter | |
typeClass2: org.scalactic.enablers.EvidenceThat[scala.collection.immutable.Vector[String]]#CanEqual[List[Int]] | |
List(1, 2, 3) should anotherFactory | |
^ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment