Skip to content

Instantly share code, notes, and snippets.

@aishfenton
Last active December 10, 2019 22:15
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb to your computer and use it in GitHub Desktop.
Save aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb to your computer and use it in GitHub Desktop.
Union Types for Scala
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