¿Cómo definir la "disyunción de tipos" (tipos de unión)?
Una forma que se ha sugerido para abordar las definiciones dobles de métodos sobrecargados es reemplazar la sobrecarga con la coincidencia de patrones:
object Bar {
def foo(xs: Any*) = xs foreach {
case _:String => println("str")
case _:Int => println("int")
case _ => throw new UglyRuntimeException()
}
}
Este enfoque requiere que abandonemos la verificación de tipos estáticos en los argumentos de foo
. Sería mucho mejor poder escribir.
object Bar {
def foo(xs: (String or Int)*) = xs foreach {
case _: String => println("str")
case _: Int => println("int")
}
}
Puedo acercarme Either
, pero se pone feo rápidamente con más de dos tipos:
type or[L,R] = Either[L,R]
implicit def l2Or[L,R](l: L): L or R = Left(l)
implicit def r2Or[L,R](r: R): L or R = Right(r)
object Bar {
def foo(xs: (String or Int)*) = xs foreach {
case Left(l) => println("str")
case Right(r) => println("int")
}
}
Parece que una solución general (elegante, eficiente) requeriría definir Either3
, Either4
, .... ¿Alguien conoce una solución alternativa para lograr el mismo fin? Que yo sepa, Scala no tiene una "disyunción de tipos" incorporada. Además, ¿las conversiones implícitas definidas anteriormente se esconden en algún lugar de la biblioteca estándar para que pueda importarlas?
Miles Sabin describe una forma muy agradable de obtener el tipo de unión en su reciente publicación de blog Tipos de unión sin caja en Scala a través del isomorfismo de Curry-Howard :
Primero define la negación de tipos como
type ¬[A] = A => Nothing
Usando la ley de De Morgan, esto le permite definir tipos de unión.
type ∨[T, U] = ¬[¬[T] with ¬[U]]
Con las siguientes construcciones auxiliares.
type ¬¬[A] = ¬[¬[A]]
type |∨|[T, U] = { type λ[X] = ¬¬[X] <:< (T ∨ U) }
Puedes escribir tipos de unión de la siguiente manera:
def size[T : (Int |∨| String)#λ](t : T) = t match {
case i : Int => i
case s : String => s.length
}
Bueno, en el caso específico de Any*
, este truco a continuación no funcionará, ya que no aceptará tipos mixtos. Sin embargo, dado que los tipos mixtos tampoco funcionarían con la sobrecarga, esto puede ser lo que desee.
Primero, declare una clase con los tipos que desea aceptar como se muestra a continuación:
class StringOrInt[T]
object StringOrInt {
implicit object IntWitness extends StringOrInt[Int]
implicit object StringWitness extends StringOrInt[String]
}
A continuación, declara foo
así:
object Bar {
def foo[T: StringOrInt](x: T) = x match {
case _: String => println("str")
case _: Int => println("int")
}
}
Y eso es. Puedes llamar foo(5)
a o foo("abc")
y funcionará, pero inténtalo foo(true)
y fallará. Esto podría evitarse mediante el código del cliente creando un StringOrInt[Boolean]
, a menos que, como señala Randall a continuación, cree StringOrInt
una sealed
clase.
Funciona porque T: StringOrInt
significa que hay un parámetro implícito de tipo StringOrInt[T]
y porque Scala busca dentro de los objetos complementarios de un tipo para ver si hay implícitos allí para hacer que el código que solicita ese tipo funcione.