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 analogousOr3
,Or4
, etc. traits. Of course, defining such traits would be much simpler than defining the correspondingEither
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".