Estoy pensando que el tipo disjunto de primera clase es un supertipo sellado, con los subtipos alternativos y las conversiones implícitas a / desde los tipos deseados de la disyunción a estos subtipos alternativos.
Supongo que esto aborda los comentarios 33 a 36 de la solución de Miles Sabin, por lo que es 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 en case Int(i)
lugar de case i : Int
.
AGREGAR: respondí a Miles Sabin en su blog de la siguiente manera. Quizás hay varias mejoras sobre cualquiera de los dos:
- 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 encuadrados implícitamente, por ejemplo, no necesita
size(Left(2))
o size(Right("test"))
.
- La sintaxis de la coincidencia de patrones está implícitamente sin caja.
- El punto de acceso de 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. Tal vez para el nombre del tipo de unión, sería mejor usar en
V
lugar de Or
, por ejemplo IntVString
, ` 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 no está en caja después del primer nivel, es decir, tiene la ventaja de ser extensible a cualquier tipo de tipos en la disyunción , mientras que Either
necesita un boxeo anidado y el paradigma en mi comentario anterior 41 no era extensible. En otras palabras, a D[Int ∨ String]
es asignable a (es decir, es un subtipo de) a 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 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
D[¬[Double]]
caso 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 adecuadamente en el tipo de entrada, porque el compilador no permitirá A
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 case _
en el get
método, para no tener que desempaquetar un Option
en el match
in size()
.
05 de marzo de 2012: la actualización previa necesita una mejora. La solución de Miles Sabin funcionó correctamente con el subtipo.
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 un tipo de sindicato cercano a la primera clase) rompió el subtipo.
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
en(() => A) => A
aparece tanto en la covariante (tipo de retorno) y contravariant (entrada de la función, o en este caso un valor de retorno de la función que es una entrada de la función) Las posiciones, por lo tanto sustituciones pueden solamente 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]]
( ver también ). Como solo necesitamos una doble contravarianza, podemos lograr el equivalente a la solución de Miles, incluso si podemos descartar el ¬
y ∨
.
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]
}
Tenga en cuenta que los 2 errores anteriores en Scala permanecen, pero el tercero se evita ya T
que ahora está limitado a ser un subtipo deA
.
Podemos confirmar que el subtipo 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 Ceilán ellos tiene , y porque en lugar de subsumir a Any
los cuales medios unboxing con un match
sobre tipos esperados puede generar un error de ejecución, el unboxing de un ( colección heterogénea que contiene a) la disyunción puede verificarse (Scala tiene que corregir los errores que señalé) Las uniones son más directas que la complejidad de usar la HList experimental de metascala para colecciones heterogéneas.
class StringOrInt[T]
se realizasealed
, la "fuga" a la que se refirió ("Por supuesto, esto podría ser eludido por el código del cliente creando unStringOrInt[Boolean]
") se conecta, al menos siStringOrInt
reside en un archivo propio. Entonces los objetos testigos deben definirse en la misma fuente queStringOrInt
.