Skip to content

Instantly share code, notes, and snippets.

@bvenners
Last active August 29, 2015 14:07
Show Gist options
  • Save bvenners/d07ca7bf2cf8021aab63 to your computer and use it in GitHub Desktop.
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 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