Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
String validation using intersection types and invariant typeclasses
Welcome to Scala version 2.11.5 (OpenJDK 64-Bit Server VM, Java 1.6.0_27).
Type in expressions to have them evaluated.
Type :help for more information.
// Define a `Parser` typeclass. This must be invariant.
scala> trait Parser[T] { def parse(s: String): Option[T] }
defined trait Parser
// Here are a couple of typeclass instances to show it working
scala> implicit val identityParser = new Parser[String] { def parse(s: String) = Some(s) }
identityParser: Parser[String]{def parse(s: String): Some[String]} = $anon$1@65d4ab0e
scala> implicit val intParser = new Parser[Int] { def parse(s: String) = try Some(s.toInt) catch { case e: Exception => None } }
intParser: Parser[Int] = $anon$1@4d91e365
// `parseAs` is a method which uses our typeclass
scala> def parseAs[T](s: String)(implicit p: Parser[T]) = p.parse(s)
parseAs: [T](s: String)(implicit p: Parser[T])Option[T]
scala> parseAs[String]("Hello world")
res0: Option[String] = Some(Hello world)
scala> parseAs[Int]("Hello world")
res1: Option[Int] = None
// Now, define an empty trait representing some constraint on our string
scala> trait UpperCase
defined trait UpperCase
// And define a corresponding parser for the intersection type `String with UpperCase`
// Note that Scala is completely happy to let you cast a `String` to a `String with UpperCase`!
scala> implicit val uppercaseParser = new Parser[String with UpperCase] { def parse(s: String) = if(s.forall(_.isUpper)) Some(s.asInstanceOf[String with UpperCase]) else None }
uppercaseParser: Parser[String with UpperCase] = $anon$1@36cb1594
// Then, watch it fail to parse!
scala> parseAs[String with UpperCase]("Hello world")
res2: Option[String with UpperCase] = None
// Here's another example, using an integer:
scala> trait Even
defined trait Even
scala> implicit val evenParser = new Parser[Int with Even] { def parse(s: String) = intParser.parse(s).flatMap { case x if x%2 == 0 => Some(x.asInstanceOf[Int with Even]); case _ => None } }
evenParser: Parser[Int with Even] = $anon$1@6d65d417
scala> parseAs[Int with Even]("2")
res3: Option[Int with Even] = Some(2)
scala> parseAs[Int with Even]("1")
res4: Option[Int with Even] = None
// Note that the types returned from `parseAs` here are subtypes of `Int` and `String`, and are therefore usable
// anywhere `Int`s and `String`s are, respectively.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment