Last active
December 10, 2019 22:15
-
-
Save aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb to your computer and use it in GitHub Desktop.
Union Types for Scala
This file contains 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
package union | |
/** | |
* Much magic. Very Lambda. Wow! | |
* | |
* These types provide union types (i.e. val a = (String or Int) within Scala. See here for good discussion on | |
* how to represent union types in Scala (hat tip Miles Sabin) | |
* http://stackoverflow.com/a/37450446/1591351 | |
* | |
* Core idea is to adapt Demorgan's Law: | |
* $$ \lnot(A \cup B) \Leftrightarrow (\lnot A \cap \lnot B) $$ | |
* | |
* To type Algerbra, which becomes: | |
* trait inv[-A] | |
* inv[A or B] :> (inv[A] with inv[B]) | |
* | |
* Where we also use a contra-variant type, inv[-A], to give us a pseudo negation. | |
* | |
* The rest is to make the recursion work, so that when we write foo[A Or B Or C] (which becomes Or[Or[A,B],C]) | |
*/ | |
object UnionType { | |
trait inv[-A] {} | |
sealed trait OrR { | |
type L <: OrR | |
type R | |
type invIntersect | |
type intersect | |
} | |
sealed class Or[A <: OrR, B] extends OrR { | |
type L = A | |
type R = B | |
type intersect = (L#intersect with R) | |
type invIntersect = (L#invIntersect with inv[R]) | |
type check[X] = invIntersect <:< inv[X] | |
} | |
object UNil extends OrR { | |
type intersect = Any | |
type invIntersect = inv[Nothing] | |
} | |
type UNil = UNil.type | |
} | |
import org.scalatest._ | |
/** | |
* Tests! | |
*/ | |
class UnionTypeSpec extends FlatSpec with Matchers { | |
import UnionType._ | |
case class Foo [A : (UNil Or Int)#check](a: A) | |
case class Foo2[A : (UNil Or Int Or String)#check](a: A) | |
case class Foo3[A : (UNil Or Int Or String Or List[Int])#check](a: A) | |
case class Foo4[A : (UNil Or Int Or String Or List[Int] Or List[String])#check](a: A) | |
case class Foo5[A : (UNil Or Int Or String Or List[Int] Or List[String] Or Symbol)#check](a: A) | |
"UnionType" should "enforce that passed in type is either A or B (or both) and not compile otherwise" in { | |
Foo(1).a should === (1) | |
"Foo(\"a\")" shouldNot typeCheck | |
"Foo(2.0)" shouldNot typeCheck | |
} | |
it should "should work with singular values too" in { | |
Foo2(1).a should === (1) | |
Foo2("a").a should === ("a") | |
"Foo2(2.0)" shouldNot typeCheck | |
"Foo2(List(1))" shouldNot typeCheck | |
} | |
it should "work with unions with >2 arity" in { | |
Foo3(1).a should === (1) | |
Foo3("a").a should === ("a") | |
Foo3(List(1)).a === (List(1)) | |
"Foo3(2.0)" shouldNot typeCheck | |
"Foo3(List(2.0))" shouldNot typeCheck | |
Foo4(1).a should === (1) | |
Foo4("a").a should === ("a") | |
Foo4(List(1)).a === (List(1)) | |
Foo4(List("a")).a === (List("a")) | |
"Foo4(2.0)" shouldNot typeCheck | |
"Foo4('a)" shouldNot typeCheck | |
Foo5(1).a should === (1) | |
Foo5("a").a should === ("a") | |
Foo5(List(1)).a === (List(1)) | |
Foo5(List("a")).a === (List("a")) | |
Foo5('a).a === ('a) | |
"Foo5(2.0)" shouldNot typeCheck | |
"Foo5(List('a))" shouldNot typeCheck | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment