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