scala - software - ¿Cómo definir "disyunción tipo"(tipos de unión)?
scala vs java (15)
Una forma que se ha sugerido para tratar con 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 entreguemos verificación de tipo estático en los argumentos para 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 a Either
, pero se pone feo rápido 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 la definición de Either3
, Either4
, ... ¿Alguien sabe de una solución alternativa para lograr el mismo fin? Que yo sepa, Scala no tiene una "disyunción de tipo" incorporada. Además, ¿están las conversiones implícitas definidas anteriormente al acecho en la biblioteca estándar en alguna parte para que yo solo pueda importarlas?
Aquí está la forma de Rex Kerr para codificar los tipos de unión. Directo y 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)
^
Fuente: Comentario n.º 27 en esta excelente publicación de blog de Miles Sabin que proporciona otra forma de codificación de tipos de unión en Scala.
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 de la siguiente manera:
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 a foo(5)
o foo("abc")
, y funcionará, pero prueba foo(true)
y fallará. Esto podría ser StringOrInt[Boolean]
por el código del cliente creando un StringOrInt[Boolean]
, a menos que, como lo señala Randall continuación, usted haga de StringOrInt
una clase sealed
.
Funciona porque T: StringOrInt
significa que hay un parámetro implícito de tipo StringOrInt[T]
, y porque Scala mira 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.
Bueno, eso es todo muy inteligente, pero estoy bastante seguro de que ya sabes que las respuestas a tus preguntas principales son varias variedades de "No". Scala maneja la sobrecarga de manera diferente y, debe admitirse, un poco menos elegante de lo que usted describe. Parte de eso se debe a la interoperabilidad de Java, parte de la cual se debe a que no se desea llegar a los casos con bordes del algoritmo de inferencia de tipo, y parte de eso se debe a que simplemente no es Haskell.
Es posible generalizar la solución de Daniel de la siguiente manera:
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")
}
}
Los principales inconvenientes de este enfoque son
- Como Daniel señaló, no maneja colecciones / varargs con tipos mixtos
- El compilador no emite una advertencia si la coincidencia no es exhaustiva
- El compilador no emite un error si la coincidencia incluye un caso imposible
- Al igual que el enfoque de
Either
, una mayor generalización requeriría la definición deOr3
,Or4
, etc. análogos. Por supuesto, la definición de tales rasgos sería mucho más simple que la definición de las clases correspondientes.
Actualizar:
Mitch Blevins demonstrates un enfoque muy similar y muestra cómo generalizarlo a más de dos tipos, llamándolo "tartamudeo".
Estoy pensando que el tipo disjunto de primera clase es un supertipo sellado, con los subtipos alternativos y las conversiones implícitas a / de los tipos deseados de la disyunción a estos subtipos alternativos.
Supongo que esto aborda los comentarios 33 - 36 de la solución de Miles Sabin, por lo que el tipo de primera clase que se puede emplear en el sitio de uso, pero no lo probé.
sealed trait IntOrString
case class IntOfIntOrString( v:Int ) extends IntOrString
case class StringOfIntOrString( v:String ) extends IntOrString
implicit def IntToIntOfIntOrString( v:Int ) = new IntOfIntOrString(v)
implicit def StringToStringOfIntOrString( v:String ) = new StringOfIntOrString(v)
object Int {
def unapply( t : IntOrString ) : Option[Int] = t match {
case v : IntOfIntOrString => Some( v.v )
case _ => None
}
}
object String {
def unapply( t : IntOrString ) : Option[String] = t match {
case v : StringOfIntOrString => Some( v.v )
case _ => None
}
}
def size( t : IntOrString ) = t match {
case Int(i) => i
case String(s) => s.length
}
scala> size("test")
res0: Int = 4
scala> size(2)
res1: Int = 2
Un problema es que Scala no empleará en el contexto de coincidencia de casos, una conversión implícita de IntOfIntOrString
a Int
(y StringOfIntOrString
a String
), por lo que debe definir extractores y usar el case Int(i)
lugar del case i : Int
.
ADD: Respondí a Miles Sabin en su blog de la siguiente manera. Quizás haya varias mejoras sobre O bien:
- Se extiende a más de 2 tipos, sin ningún ruido adicional en el sitio de uso o definición.
- Los argumentos están enmarcados implícitamente, por ejemplo, no necesitan
size(Left(2))
osize(Right("test"))
. - La sintaxis de la coincidencia de patrones se desempaqueta implícitamente.
- El punto de acceso JVM puede optimizar el boxeo y el desempaquetado.
- La sintaxis podría ser la adoptada por un futuro tipo de sindicato de primera clase, por lo que la migración podría ser perfecta? Quizás para el nombre de tipo de unión, sería mejor usar
V
lugar deOr
, por ejemploIntVString
, `Int |v| String
Int |v| String
`,`Int or String
`, o mi favorito`Int|String
`?
ACTUALIZACIÓN: Sigue la negación lógica de la disyunción para el patrón anterior, y agregué un patrón alternativo (y probablemente más útil) en el blog de Miles Sabin .
sealed trait `Int or String`
sealed trait `not an Int or String`
sealed trait `Int|String`[T,E]
case class `IntOf(Int|String)`( v:Int ) extends `Int|String`[Int,`Int or String`]
case class `StringOf(Int|String)`( v:String ) extends `Int|String`[String,`Int or String`]
case class `NotAn(Int|String)`[T]( v:T ) extends `Int|String`[T,`not an Int or String`]
implicit def `IntTo(IntOf(Int|String))`( v:Int ) = new `IntOf(Int|String)`(v)
implicit def `StringTo(StringOf(Int|String))`( v:String ) = new `StringOf(Int|String)`(v)
implicit def `AnyTo(NotAn(Int|String))`[T]( v:T ) = new `NotAn(Int|String)`[T](v)
def disjunction[T,E](x: `Int|String`[T,E])(implicit ev: E =:= `Int or String`) = x
def negationOfDisjunction[T,E](x: `Int|String`[T,E])(implicit ev: E =:= `not an Int or String`) = x
scala> disjunction(5)
res0: Int|String[Int,Int or String] = IntOf(Int|String)(5)
scala> disjunction("")
res1: Int|String[String,Int or String] = StringOf(Int|String)()
scala> disjunction(5.0)
error: could not find implicit value for parameter ev: =:=[not an Int or String,Int or String]
disjunction(5.0)
^
scala> negationOfDisjunction(5)
error: could not find implicit value for parameter ev: =:=[Int or String,not an Int or String]
negationOfDisjunction(5)
^
scala> negationOfDisjunction("")
error: could not find implicit value for parameter ev: =:=[Int or String,not an Int or String]
negationOfDisjunction("")
^
scala> negationOfDisjunction(5.0)
res5: Int|String[Double,not an Int or String] = NotAn(Int|String)(5.0)
OTRA ACTUALIZACIÓN: Con respecto a los comentarios 23 y 35 de la solución de Mile Sabin , aquí hay una manera de declarar un tipo de unión en el sitio de uso. Tenga en cuenta que se desempaquetó después del primer nivel, es decir, tiene la ventaja de ser extensible a cualquier cantidad de tipos en la disyunción , mientras que Either
necesita boxeo anidado y el paradigma en mi comentario anterior 41 no era extensible. En otras palabras, un D[Int ∨ String]
es asignable a (es decir, es un subtipo de) un D[Int ∨ String ∨ Double]
.
type ¬[A] = (() => A) => A
type ∨[T, U] = ¬[T] with ¬[U]
class D[-A](v: A) {
def get[T](f: (() => T)) = v match {
case x : ¬[T] => x(f)
}
}
def size(t: D[Int ∨ String]) = t match {
case x: D[¬[Int]] => x.get( () => 0 )
case x: D[¬[String]] => x.get( () => "" )
case x: D[¬[Double]] => x.get( () => 0.0 )
}
implicit def neg[A](x: A) = new D[¬[A]]( (f: (() => A)) => x )
scala> size(5)
res0: Any = 5
scala> size("")
error: type mismatch;
found : java.lang.String("")
required: D[?[Int,String]]
size("")
^
scala> size("hi" : D[¬[String]])
res2: Any = hi
scala> size(5.0 : D[¬[Double]])
error: type mismatch;
found : D[(() => Double) => Double]
required: D[?[Int,String]]
size(5.0 : D[?[Double]])
^
Aparentemente, el compilador de Scala tiene tres errores.
- No elegirá la función implícita correcta para ningún tipo después del primer tipo en la disyunción de destino.
- No excluye el caso
D[¬[Double]]
del partido.
3.
scala> class D[-A](v: A) {
def get[T](f: (() => T))(implicit e: A <:< ¬[T]) = v match {
case x : ¬[T] => x(f)
}
}
error: contravariant type A occurs in covariant position in
type <:<[A,(() => T) => T] of value e
def get[T](f: (() => T))(implicit e: A <:< ?[T]) = v match {
^
El método get no está restringido correctamente en el tipo de entrada, porque el compilador no permitirá A
en la posición covariante. Uno podría argumentar que es un error porque todo lo que queremos es evidencia, nunca accedemos a la evidencia en la función. Y tomé la decisión de no probar el case _
en el método get
, así que no tendría que desempaquetar una Option
en el match
en el size()
.
05 de marzo de 2012: la actualización anterior necesita una mejora. La solución de Miles Sabin funcionó correctamente con la subtipificación.
type ¬[A] = A => Nothing
type ∨[T, U] = ¬[T] with ¬[U]
class Super
class Sub extends Super
scala> implicitly[(Super ∨ String) <:< ¬[Super]]
res0: <:<[?[Super,String],(Super) => Nothing] =
scala> implicitly[(Super ∨ String) <:< ¬[Sub]]
res2: <:<[?[Super,String],(Sub) => Nothing] =
scala> implicitly[(Super ∨ String) <:< ¬[Any]]
error: could not find implicit value for parameter
e: <:<[?[Super,String],(Any) => Nothing]
implicitly[(Super ? String) <:< ?[Any]]
^
La propuesta de mi actualización anterior (para el tipo de unión de primera clase cercana) rompió la subtipificación.
scala> implicitly[D[¬[Sub]] <:< D[(Super ∨ String)]]
error: could not find implicit value for parameter
e: <:<[D[(() => Sub) => Sub],D[?[Super,String]]]
implicitly[D[?[Sub]] <:< D[(Super ? String)]]
^
El problema es que A
in (() => A) => A
aparece tanto en las posiciones covariante (tipo de retorno) como contravariante (entrada de función, o en este caso un valor de retorno de función que es una entrada de función), por lo tanto, sustituciones solo puede ser invariante
Tenga en cuenta que A => Nothing
es necesario solo porque queremos A
en la posición contravariante, de modo que los supertipos de A
no sean subtipos de D[¬[A]]
ni D[¬[A] with ¬[U]]
( vea también ) Como solo necesitamos doble contravariancia, podemos lograr una solución equivalente a la de Miles, incluso si podemos descartar el ¬
y el ∨
.
trait D[-A]
scala> implicitly[D[D[Super]] <:< D[D[Super] with D[String]]]
res0: <:<[D[D[Super]],D[D[Super] with D[String]]] =
scala> implicitly[D[D[Sub]] <:< D[D[Super] with D[String]]]
res1: <:<[D[D[Sub]],D[D[Super] with D[String]]] =
scala> implicitly[D[D[Any]] <:< D[D[Super] with D[String]]]
error: could not find implicit value for parameter
e: <:<[D[D[Any]],D[D[Super] with D[String]]]
implicitly[D[D[Any]] <:< D[D[Super] with D[String]]]
^
Entonces la solución completa es
class D[-A] (v: A) {
def get[T <: A] = v match {
case x: T => x
}
}
implicit def neg[A](x: A) = new D[D[A]]( new D[A](x) )
def size(t: D[D[Int] with D[String]]) = t match {
case x: D[D[Int]] => x.get[D[Int]].get[Int]
case x: D[D[String]] => x.get[D[String]].get[String]
case x: D[D[Double]] => x.get[D[Double]].get[Double]
}
Nótese que los 2 errores anteriores en Scala permanecen, pero se evita el tercero ya que T
está ahora restringido a ser el subtipo de A
Podemos confirmar que la subtipificación funciona.
def size(t: D[D[Super] with D[String]]) = t match {
case x: D[D[Super]] => x.get[D[Super]].get[Super]
case x: D[D[String]] => x.get[D[String]].get[String]
}
scala> size( new Super )
res7: Any = Super@1272e52
scala> size( new Sub )
res8: Any = Sub@1d941d7
He estado pensando que los tipos de intersección de primera clase son muy importantes, tanto por las razones que Ceylon los tiene , y porque en lugar de subsuming a Any
que significa unboxing con una match
en tipos esperados puede generar un error en tiempo de ejecución, el unboxing de un ( heterogéneo la colección que contiene a) disyunción puede ser verificada por tipo (Scala tiene que corregir los errores que anoté). Los sindicatos son más sencillos que la complejidad de utilizar la HList experimental de metascala para colecciones heterogéneas.
Hay otra manera que es un poco más fácil de entender si no asimilas a Curry-Howard:
type v[A,B] = Either[Option[A], Option[B]]
private def L[A,B](a: A): v[A,B] = Left(Some(a))
private def R[A,B](b: B): v[A,B] = Right(Some(b))
// TODO: for more use scala macro to generate this for up to 22 types?
implicit def a2[A,B](a: A): v[A,B] = L(a)
implicit def b2[A,B](b: B): v[A,B] = R(b)
implicit def a3[A,B,C](a: A): v[v[A,B],C] = L(a2(a))
implicit def b3[A,B,C](b: B): v[v[A,B],C] = L(b2(b))
implicit def a4[A,B,C,D](a: A): v[v[v[A,B],C],D] = L(a3(a))
implicit def b4[A,B,C,D](b: B): v[v[v[A,B],C],D] = L(b3(b))
implicit def a5[A,B,C,D,E](a: A): v[v[v[v[A,B],C],D],E] = L(a4(a))
implicit def b5[A,B,C,D,E](b: B): v[v[v[v[A,B],C],D],E] = L(b4(b))
type JsonPrimtives = (String v Int v Double)
type ValidJsonPrimitive[A] = A => JsonPrimtives
def test[A : ValidJsonPrimitive](x: A): A = x
test("hi")
test(9)
// test(true) // does not compile
He tropezado con una implementación relativamente limpia de los tipos de unión n-ary al combinar la noción de listas de tipos con una simplificación del trabajo de Miles Sabin en esta área , que alguien menciona en otra respuesta.
Dado el tipo ¬[-A]
que es contravariante en A
, por definición dado A <: B
podemos escribir ¬[B] <: ¬[A]
, invirtiendo el orden de los tipos.
Dado los tipos A
, B
y X
, queremos expresar X <: A || X <: B
X <: A || X <: B
Aplicando contravarianza, obtenemos ¬[A] <: ¬[X] || ¬[B] <: ¬[X]
¬[A] <: ¬[X] || ¬[B] <: ¬[X]
. Esto a su vez puede expresarse como ¬[A] with ¬[B] <: ¬[X]
en el que uno de A
o B
debe ser un supertipo de X
o X
sí (piense en argumentos de función).
object Union {
import scala.language.higherKinds
sealed trait ¬[-A]
sealed trait TSet {
type Compound[A]
type Map[F[_]] <: TSet
}
sealed trait ∅ extends TSet {
type Compound[A] = A
type Map[F[_]] = ∅
}
// Note that this type is left-associative for the sake of concision.
sealed trait ∨[T <: TSet, H] extends TSet {
// Given a type of the form `∅ ∨ A ∨ B ∨ ...` and parameter `X`, we want to produce the type
// `¬[A] with ¬[B] with ... <:< ¬[X]`.
type Member[X] = T#Map[¬]#Compound[¬[H]] <:< ¬[X]
// This could be generalized as a fold, but for concision we leave it as is.
type Compound[A] = T#Compound[H with A]
type Map[F[_]] = T#Map[F] ∨ F[H]
}
def foo[A : (∅ ∨ String ∨ Int ∨ List[Int])#Member](a: A): String = a match {
case s: String => "String"
case i: Int => "Int"
case l: List[_] => "List[Int]"
}
foo(42)
foo("bar")
foo(List(1, 2, 3))
foo(42d) // error
foo[Any](???) // error
}
Pasé un tiempo tratando de combinar esta idea con un límite superior en los tipos de miembros como se ve en los TList
s de harrah/up , sin embargo, la implementación de Map
with type bounds hasta ahora ha sido un desafío.
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
utilizando 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) }
puede 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
}
Nos gustaría un operador de tipo Or[U,V]
que se pueda usar para restringir un tipo de parámetros X
de tal manera que X <: U
o X <: V
Aquí hay una definición que se acerca lo más posible:
trait Inv[-X]
type Or[U,T] = {
type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X]
}
Así es como se usa:
// use
class A; class B extends A; class C extends B
def foo[X : (B Or String)#pf] = {}
foo[B] // OK
foo[C] // OK
foo[String] // OK
foo[A] // ERROR!
foo[Number] // ERROR!
Esto usa algunos trucos tipo Scala. El principal es el uso de restricciones de tipo generalizadas . Dados los tipos U
y V
, el compilador de Scala proporciona una clase llamada U <:< V
(y un objeto implícito de esa clase) si y solo si el compilador de Scala puede demostrar que U
es un subtipo de V
Aquí hay un ejemplo más simple usando restricciones de tipo generalizadas que funciona para algunos casos:
def foo[X](implicit ev : (B with String) <:< X) = {}
Este ejemplo funciona cuando X
una instancia de la clase B
, una String
o tiene un tipo que no es un supertipo ni un subtipo de B
o String
. En los primeros dos casos, es verdad por la definición de la palabra clave with
(B with String) <: B
y (B with String) <: String
, por lo que Scala proporcionará un objeto implícito que se pasará como ev
: el El compilador Scala aceptará correctamente foo[B]
y foo[String]
.
En el último caso, estoy confiando en el hecho de que si U with V <: X
, entonces U <: X
o V <: X
Parece intuitivamente cierto, y simplemente lo estoy asumiendo. Esta suposición deja claro por qué este ejemplo simple falla cuando X
es un supertipo o subtipo de B
o String
: por ejemplo, en el ejemplo anterior, foo[A]
se acepta incorrectamente y foo[C]
se rechaza incorrectamente. De nuevo, lo que queremos es algún tipo de expresión de tipo en las variables U
, V
y X
que es verdadera exactamente cuando X <: U
o X <: V
La noción de contravariancia de Scala puede ayudar aquí. ¿Recuerdas el rasgo de trait Inv[-X]
? Porque es contravariante en su parámetro de tipo X
, Inv[X] <: Inv[Y]
si y solo si Y <: X
Eso significa que podemos reemplazar el ejemplo anterior por uno que realmente funcione:
trait Inv[-X]
def foo[X](implicit ev : (Inv[B] with Inv[String]) <:< Inv[X]) = {}
Esto se debe a que la expresión (Inv[U] with Inv[V]) <: Inv[X]
es verdadera, según el mismo supuesto anterior, exactamente cuando Inv[U] <: Inv[X]
o Inv[V] <: Inv[X]
, y según la definición de contravarianza, esto es verdadero exactamente cuando X <: U
o X <: V
Es posible hacer las cosas un poco más reutilizables declarando un tipo parametrizable BOrString[X]
y usándolo de la siguiente manera:
trait Inv[-X]
type BOrString[X] = (Inv[B] with Inv[String]) <:< Inv[X]
def foo[X](implicit ev : BOrString[X]) = {}
Scala ahora intentará construir el tipo BOrString[X]
para cada X
que se llame a foo
, y el tipo se construirá precisamente cuando X
es un subtipo de B
o String
. Eso funciona, y hay una notación abreviada. La sintaxis a continuación es equivalente (excepto que ev
debe ahora ser referenciado en el cuerpo del método como implicitly[BOrString[X]]
lugar de simplemente ev
) y usa BOrString
como un tipo vinculado al contexto :
def foo[X : BOrString] = {}
Lo que realmente nos gustaría es una forma flexible de crear un contexto de tipo vinculado. Un contexto tipo debe ser un tipo parametrizable, y queremos una forma parametrizable para crear uno. Parece que estamos tratando de curry funciones en tipos al igual que curry funciones en valores. En otras palabras, nos gustaría algo como lo siguiente:
type Or[U,T][X] = (Inv[U] with Inv[T]) <:< Inv[X]
Eso no es directamente posible en Scala, pero hay un truco que podemos usar para acercarnos mucho. Eso nos lleva a la definición de Or
arriba:
trait Inv[-X]
type Or[U,T] = {
type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X]
}
Aquí usamos el tipado estructural y el operador de libra de Scala para crear un tipo estructural Or[U,T]
que garantiza tener un tipo interno. Esta es una bestia extraña. Para dar un contexto, la función def bar[X <: { type Y = Int }](x : X) = {}
debe AnyRef
con subclases de AnyRef
que tengan un tipo Y
definido en ellas:
bar(new AnyRef{ type Y = Int }) // works!
El uso del operador de libra nos permite referirnos al tipo interno Or[B, String]#pf
, y al usar la notación infija para el operador de tipo Or
, llegamos a nuestra definición original de foo
:
def foo[X : (B Or String)#pf] = {}
Podemos utilizar el hecho de que los tipos de función son contravariantes en su primer parámetro de tipo para evitar definir el rasgo Inv
:
type Or[U,T] = {
type pf[X] = ((U => _) with (T => _)) <:< (X => _)
}
Puede echar un vistazo a MetaScala , que tiene algo llamado OneOf
. Me da la impresión de que esto no funciona bien con las declaraciones de match
, pero que puede simular la coincidencia utilizando funciones de orden superior. Eche un vistazo a este fragmento , por ejemplo, pero tenga en cuenta que la parte de "coincidencia simulada" está comentada, tal vez porque todavía no funciona.
Ahora para algunos editorializadores: no creo que haya nada atroz en la definición de Either3, Either4, etc., como usted describe. Esto es esencialmente dual a los 22 tipos de tupla estándar integrados en Scala. Ciertamente estaría bien si Scala tuviera tipos disyuntivos incorporados, y tal vez alguna buena sintaxis para ellos como {x, y, z}
.
También hay este truco :
implicit val x: Int = 0
def foo(a: List[Int])(implicit ignore: Int) { }
implicit val y = ""
def foo(a: List[String])(implicit ignore: String) { }
foo(1::2::Nil)
foo("a"::"b"::Nil)
Consulte Trabajar alrededor de las ambigüedades de borrado de tipo (Scala) .
Una solución de clase de tipo es probablemente la mejor manera de ir aquí, utilizando implícitos. Esto es similar al enfoque monoide mencionado en el libro de Odersky / Spoon / Venners:
abstract class NameOf[T] {
def get : String
}
implicit object NameOfStr extends NameOf[String] {
def get = "str"
}
implicit object NameOfInt extends NameOf[Int] {
def get = "int"
}
def printNameOf[T](t:T)(implicit name : NameOf[T]) = println(name.get)
Si luego ejecuta esto en REPL:
scala> printNameOf(1)
int
scala> printNameOf("sss")
str
scala> printNameOf(2.0f)
<console>:10: error: could not find implicit value for parameter nameOf: NameOf[
Float]
printNameOf(2.0f)
^
Dotty , un nuevo compilador de Scala experimental, admite tipos de unión (escrita A | B
), por lo que puede hacer exactamente lo que desea:
def foo(xs: (String | Int)*) = xs foreach {
case _: String => println("str")
case _: Int => println("int")
}
Adding to the already great answers here. Here''s a gist that builds on Miles Sabin union types (and Josh''s ideas) but also makes them recursively defined, so you can have >2 types in the union ( def foo[A : UNil Or Int Or String Or List[String]
)
https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb
NB: I should add that after playing around with the above for a project, I ended up going back to plain-old-sum-types (ie sealed trait with subclasses). Miles Sabin union types are great for restricting the type parameter, but if you need to return a union type then it doesn''t offer much.
From the docs , with the addition of sealed
:
sealed class Expr
case class Var (x: String) extends Expr
case class Apply (f: Expr, e: Expr) extends Expr
case class Lambda(x: String, e: Expr) extends Expr
Regarding the sealed
part:
Es posible definir otras clases de casos que amplíen el tipo Expr en otras partes del programa (...). Esta forma de extensibilidad se puede excluir declarando la clase base Expr sellada; en este caso, todas las clases que extienden directamente Expr deben estar en el mismo archivo fuente que Expr.