Skip to content

Instantly share code, notes, and snippets.

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