a choice function defined for untagged unions
you can implement a statically typed equivalent of of the "switch" statement based on the concept of anonymous sum types, like so:
import scala.reflect.runtime.universe._
object Main extends App {
* consider an "untagged union": a value of one of a known set of types,
* which can be discriminated upon such that it is possible to write the
* "choice" function for any given arity. an example for given types:
* choice2(value: UU[Int,Float], f1: Int => T, f2: Float => T): T = {
* if (value is Int) f1(value)
* if (value is Float) f2(value)
* choice3(value: UU[A,B,C], f1: A => R, f2: B => R, f3: C => R): R = {
* if (value is A) f1(value)
* ideally, we could then write code like this:
* val x = UU[Int,String](1, "i'm a string")
* val y = choice2(x, "INT: " + _.toString, "STR: " + _)
* note: it's tempting to define an untagged union not as a set but an
* ordered list, so that we could have Int | Float | Int - but this is
* meaningless both in practice and in theory. the only way to construct
* a value of this type would be to give it an index- a tag. in type theory,
* a *tagged union* or *sum type* is defined by its introduction forms
* (constructors), and an *untagged union* or *disjoint* is always a set.
// here's the existing tagged union mechanism:
sealed abstract class FooBase
case class Foo1(i: Int) extends FooBase
case class Foo2(s: String) extends FooBase
// a hardcoded function equivalent to choice2:
def fooChoice2[TReturn](v: FooBase, f1: Int => TReturn, f2: String => TReturn) = v match {
// and some helper functions
def printInt(i: Int) = "INT: " + i.toString
def printStr(s: String) = "STR: " + s
def printDbl(d: Double) = "DBL: " + d.toString
val fooY = fooChoice2(fooX, printInt, printStr)
println(fooY) //prints STR: foo
// can fooChoice2 be generalised to apply to any tagged union of appropriate arity?
def choice2a[TReturn, TBase, TU1, TU2](v: TBase, f1: TU1 => TReturn, f2: TU2 => TReturn) = v match {
//case TU1(i) => f1(TU1(i))
//case TU2(s) => f2(TU2(s))
case _ => error("pattern match fails to compile - because type TU1 is not a value")
* nope; scala's generics are more like haskell compile-only inference than
* C# runtime-reification. so let's abandon case classes, and supply implicit
* "type tags. this is type-introspective, but done by the compiler and optimised
* in this case to a switch on constants - no runtime lookup
def choice2b[TReturn, T1, T2, TArg]
(v: TArg, f1: T1 => TReturn, f2: T2 => TReturn)
(implicit manifest:TypeTag[TArg], m1:TypeTag[T1], m2:TypeTag[T2]) = manifest match {
case `m1` => f1(v.asInstanceOf[T1])
case `m2` => f2(v.asInstanceOf[T2])
val y2 = choice2b(x2, printStr, printInt)
println(y2) //prints INT: 4
// seems to work. here's the choice3 version to prove that it can be linearly extended:
def choice3b[TReturn, T1, T2, T3, TArg]
(v: TArg, f1: T1 => TReturn, f2: T2 => TReturn, f3: T3 => TReturn)
(implicit mv:TypeTag[TArg], m1:TypeTag[T1], m2:TypeTag[T2], m3:TypeTag[T3]) = mv match {
case `m1` => f1(v.asInstanceOf[T1])
case `m2` => f2(v.asInstanceOf[T2])
case `m3` => f3(v.asInstanceOf[T3])
val y3 = choice3b(x3, printStr, printInt, printDbl)
println(y3) //prints DBL: 2.0
* so, that's pretty nice to use. it effectively gives us the ability to write
* where f(x) or g(x) is called depending on the types of x, f and g.
* but how type-safe is it? the following call will fail with a runtime error:
* choice2b(1.0, printStr, printInt)
* what we need is some way to verify at compile time that TArg is in the set of
* types T1, T2, etc- the Untagged Union. given a sufficiently powerful language
* this can be done without special facilities.
//the union type, and a singleton which provides implicit views as a union
//for either member of the type
implicit def _1[T1,T2](v1: T1) = new UU2[T1,T2]
implicit def _2[T1,T2](v2: T2) = new UU2[T1,T2]
//the final version of choice2 has a *view bound* on the type, requiring
//TArg to be convertible to a UU2, and therefore requiring it to be either
//a T1 or a T2 as above. UU2's implicits are brought into scope by the
def choice2c[TReturn, T1, T2, TArg <% UU2[T1,T2]]
(v: TArg, f1: T1 => TReturn, f2: T2 => TReturn)
(implicit manifest:TypeTag[TArg], m1:TypeTag[T1], m2:TypeTag[T2]) = v match {
case v1:T1 if manifest.tpe =:= m1.tpe => f1(v1)
case v2:T2 if manifest.tpe =:= m2.tpe => f2(v2)
println(choice2c(0.0, printStr, printDbl)) //DBL: 0.0
//println(choice2c(0.0, printStr, printInt)) //does not compile
//and so on for higher arities:
implicit def _1[T1,T2,T3](v1: T1) = new UU3[T1,T2,T3]
implicit def _2[T1,T2,T3](v2: T2) = new UU3[T1,T2,T3]
implicit def _3[T1,T2,T3](v3: T3) = new UU3[T1,T2,T3]
def choice3c[TReturn, T1, T2, T3, TArg <% UU3[T1,T2,T3]]
(v: TArg, f1: T1 => TReturn, f2: T2 => TReturn, f3: T3 => TReturn)
(implicit manifest:TypeTag[TArg], m1:TypeTag[T1], m2:TypeTag[T2], m3:TypeTag[T3]) = v match {
case v1:T1 if manifest.tpe =:= m1.tpe => f1(v1)
case v2:T2 if manifest.tpe =:= m2.tpe => f2(v2)
case v3:T3 if manifest.tpe =:= m3.tpe => f3(v3)
println(choice3c("three choices", printStr, printDbl, printInt))
println(choice3c(3, printStr, printDbl, printInt))
println(choice3c(3.0, printStr, printDbl, printInt))
* we've achieved the mooted benefits of "anonymous sum types"
* without actual language support. what's actually been created here
* is similar, however: a generic type which provides a proved
* view, allowing set of consistent type bounds to refer to membership
* the really cool thing is that this technique is composable and
* generalisable- it can be used recursively to create larger-arity
* unions without excess boilerplate, and it can be used in monadic
* type constructors such as Option[T] to provide safer switches.
* if you use concrete types, you don't even need the manifests:
def doSomethingWithAStringOrAnInt[T <% UU2[String,Int]](x: Option[T]) = x match {
case Some(i: Int) => "int"
case Some(s: String) => "string"
case None => "not int or string"
doSomethingWithAStringOrAnInt(Some(50)) //compiles
//doSomethingWithAStringOrAnInt(Some(50.0)) does not compile