scala - How to define "type disjunction" (union types)?

ID : 20073

viewed : 9

Tags : scalascala

Top 5 Answer for scala - How to define "type disjunction" (union types)?

vote vote


Miles Sabin describes a very nice way to get union type in his recent blog post Unboxed union types in Scala via the Curry-Howard isomorphism:

He first defines negation of types as

type ¬[A] = A => Nothing 

using De Morgan's law this allows him to define union types

type ∨[T, U] = ¬[¬[T] with ¬[U]] 

With the following auxiliary constructs

type ¬¬[A] = ¬[¬[A]] type |∨|[T, U] = { type λ[X] = ¬¬[X] <:< (T ∨ U) } 

you can write union types as follows:

def size[T : (Int |∨| String)#λ](t : T) = t match {     case i : Int => i     case s : String => s.length } 
vote vote


Well, in the specific case of Any*, this trick below won't work, as it will not accept mixed types. However, since mixed types wouldn't work with overloading either, this may be what you want.

First, declare a class with the types you wish to accept as below:

class StringOrInt[T] object StringOrInt {   implicit object IntWitness extends StringOrInt[Int]   implicit object StringWitness extends StringOrInt[String] } 

Next, declare foo like this:

object Bar {   def foo[T: StringOrInt](x: T) = x match {     case _: String => println("str")     case _: Int => println("int")   } } 

And that's it. You can call foo(5) or foo("abc"), and it will work, but try foo(true) and it will fail. This could be side-stepped by the client code by creating a StringOrInt[Boolean], unless, as noted by Randall below, you make StringOrInt a sealed class.

It works because T: StringOrInt means there's an implicit parameter of type StringOrInt[T], and because Scala looks inside companion objects of a type to see if there are implicits there to make code asking for that type work.

vote vote


Dotty, a new experimental Scala compiler, supports union types (written A | B), so you can do exactly what you wanted:

def foo(xs: (String | Int)*) = xs foreach {    case _: String => println("str")    case _: Int => println("int") } 
vote vote


Here is the Rex Kerr way to encode union types. Straight and simple!

scala> def f[A](a: A)(implicit ev: (Int with String) <:< A) = a match {      |   case i: Int => i + 1      |   case s: String => s.length      | } f: [A](a: A)(implicit ev: <:<[Int with String,A])Int  scala> f(3) res0: Int = 4  scala> f("hello") res1: Int = 5  scala> f(9.2) <console>:9: error: Cannot prove that Int with String <:< Double.        f(9.2)         ^ 

Source: Comment #27 under this excellent blog post by Miles Sabin which provides another way of encoding union types in Scala.

vote vote


It's possible to generalize Daniel's solution as follows:

sealed trait Or[A, B]  object Or {    implicit def a2Or[A,B](a: A) = new Or[A, B] {}    implicit def b2Or[A,B](b: B) = new Or[A, B] {} }  object Bar {    def foo[T <% String Or Int](x: T) = x match {      case _: String => println("str")      case _: Int => println("int")    } } 

The main drawbacks of this approach are

  • As Daniel pointed out, it does not handle collections/varargs with mixed types
  • The compiler does not issue a warning if the match is not exhaustive
  • The compiler does not issue an error if the match includes an impossible case
  • Like the Either approach, further generalization would require defining analogous Or3, Or4, etc. traits. Of course, defining such traits would be much simpler than defining the corresponding Either classes.


Mitch Blevins demonstrates a very similar approach and shows how to generalize it to more than two types, dubbing it the "stuttering or".

Top 3 video Explaining scala - How to define "type disjunction" (union types)?