¿Cómo definir la "disyunción de tipos" (tipos de unión)?

Resuelto Aaron Novstrup asked hace 14 años • 16 respuestas

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?

Aaron Novstrup avatar Aug 18 '10 07:08 Aaron Novstrup
Aceptado

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] <:< (TU) }

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
}
michid avatar Jun 10 '2011 22:06 michid

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 fooasí:

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 StringOrIntuna sealedclase.

Funciona porque T: StringOrIntsignifica 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.

Daniel C. Sobral avatar Aug 18 '2010 02:08 Daniel C. Sobral