Last active
August 29, 2015 14:15
-
-
Save propensive/61214bf1b7a7c4a742ae to your computer and use it in GitHub Desktop.
String validation using intersection types and invariant typeclasses
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
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