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

Solution 1:

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
}

Solution 2:

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.

Solution 3:

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")
}

Solution 4:

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.

Solution 5:

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.

Update:

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