Last active
August 29, 2015 14:07
-
-
Save bvenners/d07ca7bf2cf8021aab63 to your computer and use it in GitHub Desktop.
Demo of EquaSets, sets based on alternate equalities, in Scalactic 3.0
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
// | |
// This gist demos the EquaSet type coming in Scalactic 3.0, which is a set that | |
// can use an alternate equality for determining set membership. | |
// | |
// The Scaladoc for this is version of Scalactic here: | |
// | |
// http://www.artima.com/docs-scalatest-3.0.0-SNAP1/#package | |
// | |
// A screencast that includes an EquaSets demo is here: | |
// | |
// https://www.parleys.com/play/5442985ae4b06e1184ae4299/about | |
// | |
// EquaSets uses path dependent types to ensure that two different | |
// EquaSet instances that have different notions of set membership | |
// cannot be unioned, intersected, or diffed, a technique suggested | |
// by Martin Odersky. | |
// | |
scala> import org.scalactic._ | |
import org.scalactic._ | |
// First you must create an EquaSets and give it a HashingEquality, or create | |
// a SortedEquaSets and give it an OrderingEquality. | |
scala> val trimmed = EquaSets[String](StringNormalizations.trimmed.toHashingEquality) | |
trimmed: org.scalactic.EquaSets[String] = org.scalactic.EquaSets@43349eef | |
scala> val lowered = SortedEquaSets[String](StringNormalizations.lowerCased.toOrderingEquality) | |
lowered: org.scalactic.SortedEquaSets[String] = org.scalactic.SortedEquaSets@20836bc5 | |
// You can then create multiple EquaSet instances in the context of the EquaSets instances. | |
// The equality used by an EquaSet to determine set membership will be the one captured by | |
// its enclosing EquaSets. | |
scala> val trimmedHa = trimmed.EquaSet("ha") | |
trimmedHa: trimmed.EquaSet = EquaSet(ha) | |
scala> val trimmedHaHa = trimmed.EquaSet(" ha ", "HA") | |
trimmedHaHa: trimmed.EquaSet = EquaSet( ha , HA) | |
scala> val loweredHa = lowered.SortedEquaSet("ha") | |
loweredHa: lowered.SortedEquaSet = TreeEquaSet(ha) | |
scala> val loweredHaHa = lowered.EquaSet(" ha ", "HA") | |
loweredHaHa: lowered.EquaSet = EquaSet( ha , HA) | |
// The use of the captured equality can be seen in the union, intersect, | |
// and diff operations. The equality captured in the trimmed EquaSets ignores | |
// leading and trailing whitespace, so it considers " ha " to equal "ha". | |
scala> trimmedHa union trimmedHaHa | |
res0: trimmed.EquaSet = EquaSet(ha, HA) | |
// The equality captured in the lowered EquaSets ignores | |
// case, so it considers "HA" to equal "ha". | |
scala> loweredHa union loweredHaHa | |
res1: lowered.SortedEquaSet = TreeEquaSet( ha , ha) | |
// If you try to union to EquaSets that have different notions | |
// of set membership, it won't compile: | |
scala> trimmedHa union loweredHa | |
<console>:18: error: type mismatch; | |
found : lowered.SortedEquaSet | |
required: trimmed.EquaSet | |
trimmedHa union loweredHa | |
^ | |
// The intersect operation behaves similarly to union (except it intersects). | |
scala> trimmedHa intersect trimmedHaHa | |
res3: trimmed.EquaSet = EquaSet(ha) | |
scala> loweredHa intersect loweredHaHa | |
res4: lowered.SortedEquaSet = TreeEquaSet(ha) | |
scala> trimmedHa intersect loweredHa | |
<console>:18: error: type mismatch; | |
found : lowered.SortedEquaSet | |
required: trimmed.EquaSet | |
trimmedHa intersect loweredHa | |
^ | |
// Can also get a scala.collection.immutable.Set of boxed values | |
// that use the alternate equality. In other words the actual value | |
// sits inside an "EquaBox", and the EquaBoxes sit inside the Scala | |
// Set. Thus the equals method of the EquaBox is used to determine | |
// set membership, and it delegates to the captured alternate equality | |
// of the enclosing EquaSets. | |
scala> val trimmedBoxedHa = trimmedHa.toEquaBoxSet | |
trimmedBoxedHa: scala.collection.immutable.Set[trimmed.EquaBox] = Set(EquaBox(ha)) | |
scala> val trimmedBoxedHaHa = trimmedHaHa.toEquaBoxSet | |
trimmedBoxedHaHa: scala.collection.immutable.Set[trimmed.EquaBox] = Set(EquaBox( ha ), EquaBox(HA)) | |
scala> val loweredBoxedHa = loweredHa.toEquaBoxSet | |
loweredBoxedHa: scala.collection.immutable.SortedSet[lowered.EquaBox] = TreeSet(EquaBox(ha)) | |
scala> val loweredBoxedHaHa = loweredHaHa.toEquaBoxSet | |
loweredBoxedHaHa: scala.collection.immutable.Set[lowered.EquaBox] = Set(EquaBox( ha ), EquaBox(HA)) | |
// As a result, union on Scala sets now adheres to the alternate equality. | |
scala> trimmedBoxedHa union trimmedBoxedHaHa | |
res6: scala.collection.immutable.Set[trimmed.EquaBox] = Set(EquaBox(ha), EquaBox(HA)) | |
scala> loweredBoxedHa union loweredBoxedHaHa | |
res7: scala.collection.immutable.SortedSet[lowered.EquaBox] = TreeSet(EquaBox( ha ), EquaBox(ha)) | |
// And trying to union two Scala EquaBox Sets with different notions of equality | |
// will not compile: | |
scala> trimmedBoxedHa union loweredBoxedHa | |
<console>:17: error: type mismatch; | |
found : scala.collection.immutable.SortedSet[lowered.EquaBox] | |
required: scala.collection.GenSet[trimmed.EquaBox] | |
trimmedBoxedHa union loweredBoxedHa | |
^ | |
// Intersect on Scala Set's of EquaBoxes works similarly: | |
scala> trimmedBoxedHa intersect trimmedBoxedHaHa | |
res9: scala.collection.immutable.Set[trimmed.EquaBox] = Set(EquaBox(ha)) | |
scala> loweredBoxedHa intersect loweredBoxedHaHa | |
res10: scala.collection.immutable.SortedSet[lowered.EquaBox] = TreeSet(EquaBox(ha)) | |
scala> trimmedBoxedHa intersect loweredBoxedHa | |
<console>:17: error: type mismatch; | |
found : scala.collection.immutable.SortedSet[lowered.EquaBox] | |
required: scala.collection.GenSet[trimmed.EquaBox] | |
trimmedBoxedHa intersect loweredBoxedHa | |
^ | |
// map, flatMap, and filter can be invoked directly on an EquaSet if you want | |
// to stay in the same enclosing EquaSets: | |
scala> loweredHaHa.map(_ + "!") | |
res12: lowered.EquaSet = EquaSet( ha !, HA!) | |
// If you want to map (for example) into a different EquaSets, however, you need to first say | |
// which EquaSets you want to map into by passing the EquaSets to into: | |
scala> loweredHaHa.into(trimmed).map(_ + "!") | |
res13: trimmed.EquaSet = EquaSet( ha !, HA!) | |
// This is the build.sbt I used to generate the above REPL session. Just | |
// place this in a directory and say: | |
// $ sbt | |
// ... | |
// > console | |
scalaVersion := "2.11.2" | |
libraryDependencies += "org.scalactic" % "scalactic_2.11" % "3.0.0-SNAP1" | |
libraryDependencies += "org.scalatest" % "scalatest_2.11" % "3.0.0-SNAP1" % "test" | |
initialCommands in console := "import org.scalactic._" | |
initialCommands in Test in console := """|import org.scalatest._ | |
|import org.scalactic._ | |
|import Matchers._""".stripMargin | |
resolvers += "Sonatype OSS Releases" at "https://oss.sonatype.org/content/repositories/releases" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment