Skip to content

Instantly share code, notes, and snippets.

@bvenners
Last active August 29, 2015 14:04
Show Gist options
  • Save bvenners/eb746eb23af8f8373d74 to your computer and use it in GitHub Desktop.
Save bvenners/eb746eb23af8f8373d74 to your computer and use it in GitHub Desktop.
Types involved in ScalaTest matcher expressions
//
// 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