Una forma sugerida 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 la comprobación de tipo estático en los argumentos 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á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")
}
}
Se ve como una solución general (elegante, eficiente) requeriría definir Either3
, Either4
, .... ¿Alguien sabe de una solución alternativa para lograr el mismo fin? Que yo sepa, Scala no tiene "disyunción de tipo" incorporada. Además, ¿están las conversiones implícitas definidas anteriormente al acecho en la biblioteca estándar en algún lugar para que pueda importarlas?
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
.Either
enfoque parece ser que perdemos mucho soporte del compilador para verificar la coincidencia.trait StringOrInt ...
StringOrInt[T]
aStringOrInt[-T]
(consulte stackoverflow.com/questions/24387701/… )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
usando la ley de De Morgan esto le permite definir tipos de unión
Con las siguientes construcciones auxiliares
puede escribir tipos de unión de la siguiente manera:
fuente
Dotty , un nuevo compilador experimental de Scala, admite tipos de unión (escritos
A | B
), por lo que puede hacer exactamente lo que quería:fuente
Aquí está la forma de Rex Kerr para codificar tipos de unión. ¡Directo y simple!
Fuente: Comentario # 27 en esta excelente publicación de blog de Miles Sabin que proporciona otra forma de codificar tipos de unión en Scala.
fuente
scala> f(9.2: AnyVal)
pasa el typechecker.trait Contra[-A] {}
en lugar de todas las funciones para nada. Entonces obtienes cosas comotype Union[A,B] = { type Check[Z] = Contra[Contra[Z]] <:< Contra[Contra[A] with Contra[B]] }
usadasdef f[T: Union[Int, String]#Check](t: T) = t match { case i: Int => i; case s: String => s.length }
(sin unicode elegante).Es posible generalizar la solución de Daniel de la siguiente manera:
Los principales inconvenientes de este enfoque son
Either
enfoque, más generalización requeriría la definición análogaOr3
,Or4
, etc. rasgos. Por supuesto, definir tales rasgos sería mucho más simple que definir lasEither
clases correspondientes .Actualizar:
Mitch Blevins demuestra un enfoque muy similar y muestra cómo generalizarlo a más de dos tipos, denominándolo "tartamudeo".
fuente
Me he topado con una implementación relativamente limpia de los tipos de unión n-aria 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 contravarianteA
, por definiciónA <: B
, podemos escribir¬[B] <: ¬[A]
, invirtiendo el orden de los tipos.Tipos dados
A
,B
yX
, que quieren expresarX <: A || X <: B
. Aplicando contravarianza, obtenemos¬[A] <: ¬[X] || ¬[B] <: ¬[X]
. Esto a su vez puede ser expresado como¬[A] with ¬[B] <: ¬[X]
en la que uno deA
oB
debe ser un subtipo de élX
o deX
ella misma (pensar en argumentos de la función).Pasé algún tiempo tratando de combinar esta idea con un límite superior en los tipos de miembros como se ve en el
TList
s de harrah / up , sin embargo, la implementación deMap
con límites de tipo hasta ahora ha resultado un desafío.fuente
Function1
como un tipo contravariante existente. No necesita una implementación, todo lo que necesita es evidencia de conformidad (<:<
).Una solución de clase de tipo es probablemente la mejor manera de ir aquí, usando implicits. Esto es similar al enfoque monoide mencionado en el libro Odersky / Spoon / Venners:
Si luego ejecuta esto en REPL:
fuente
Either
tipo preexistente de Scala tiende a reforzar esta creencia. El uso de clases de tipos a través de los implícitos de Scala es una mejor solución al problema subyacente, pero es un concepto relativamente nuevo y aún no ampliamente conocido, por lo que el OP ni siquiera sabía considerarlos como una posible alternativa a un tipo de unión.Nos gustaría un operador de tipo
Or[U,V]
que se pueda utilizar para restringir los parámetros de un tipoX
de tal manera queX <: U
oX <: V
. Aquí hay una definición que se acerca lo más posible:Así es como se usa:
Esto usa algunos trucos de tipo Scala. El principal es el uso de restricciones de tipo generalizadas . Dados tipos
U
yV
, el compilador Scala proporciona una clase llamadaU <:< V
(y un objeto implícito de esa clase) si y solo si el compilador Scala puede probar queU
es un subtipo deV
. Aquí hay un ejemplo más simple que usa restricciones de tipo generalizadas que funciona para algunos casos:Este ejemplo funciona cuando
X
una instancia de claseB
, aString
o tiene un tipo que no es ni un supertipo ni un subtipo deB
oString
. En los primeros dos casos, es cierto por la definición de lawith
palabra clave que(B with String) <: B
y(B with String) <: String
, por lo que Scala proporcionará un objeto implícito que se pasará comoev
: el compilador de Scala aceptará correctamentefoo[B]
yfoo[String]
.En el último caso, estoy confiando en el hecho de que si
U with V <: X
, entoncesU <: X
oV <: X
. Parece intuitivamente cierto, y simplemente lo estoy asumiendo. A partir de esta suposición, está claro por qué este ejemplo simple falla cuandoX
es un supertipo o subtipo de cualquieraB
oString
: por ejemplo, en el ejemplo anterior,foo[A]
se acepta incorrectamente yfoo[C]
se rechaza incorrectamente. Una vez más, lo que queremos es una especie de expresión de tipo de las variablesU
,V
yX
que es cierto exactamente cuándoX <: U
oX <: V
.La noción de contravarianza de Scala puede ayudar aquí. ¿Recuerdas el rasgo
trait Inv[-X]
? Porque es contravariante en su parámetro de tipoX
,Inv[X] <: Inv[Y]
si y solo siY <: X
. Eso significa que podemos reemplazar el ejemplo anterior con uno que realmente funcione:Esto se debe a que la expresión
(Inv[U] with Inv[V]) <: Inv[X]
es verdadera, por el mismo supuesto anterior, exactamente cuándoInv[U] <: Inv[X]
oInv[V] <: Inv[X]
, y por la definición de contravarianza, esto es cierto exactamente cuándoX <: U
oX <: V
.Es posible hacer las cosas un poco más reutilizables declarando un tipo parametrizable
BOrString[X]
y usándolo de la siguiente manera:Scala ahora intentará construir el tipo
BOrString[X]
para cadaX
quefoo
se llama con, y el tipo será construido precisamente cuandoX
es un subtipo de cualquieraB
oString
. Eso funciona, y hay una notación abreviada. La siguiente sintaxis es equivalente (excepto queev
ahora se debe hacer referencia en el cuerpo del método comoimplicitly[BOrString[X]]
algo más que simpleev
) y se utilizaBOrString
como un tipo de contexto vinculado :Lo que realmente nos gustaría es una forma flexible de crear un tipo de contexto vinculado. Un contexto de tipo debe ser un tipo parametrizable, y queremos una forma parametrizable de crear uno. Eso suena como 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:
Eso no es directamente posible en Scala, pero hay un truco que podemos usar para acercarnos bastante. Eso nos lleva a la definición de
Or
arriba:Aquí usamos la tipificación 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 algo de contexto, la funcióndef bar[X <: { type Y = Int }](x : X) = {}
debe llamarse con subclasesAnyRef
que tengan un tipoY
definido en ellas: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 tipoOr
, llegamos a nuestra definición original defoo
: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
:fuente
A|B <: A|B|C
problema? stackoverflow.com/questions/45255270/… No puedo decirlo.También existe este truco :
Consulte Cómo solucionar las ambigüedades de borrado de tipo (Scala) .
fuente
(implicit e: DummyImplicit)
a una de las firmas tipo.Puede echar un vistazo a MetaScala , que tiene algo llamado
OneOf
. Tengo la impresión de que esto no funciona bien con lasmatch
declaraciones, pero que puede simular coincidencias utilizando funciones de orden superior. Echa 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 del todo.Ahora para un poco de editorialización: no creo que haya nada atroz en definir Either3, Either4, etc., como usted describe. Esto es esencialmente dual a los 22 tipos de tuplas estándar integrados en Scala. Ciertamente sería bueno si Scala tuviera tipos disyuntivos incorporados, y tal vez alguna buena sintaxis para ellos
{x, y, z}
.fuente
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é.
Un problema es que Scala no empleará en el contexto de coincidencia de casos, una conversión implícita de
IntOfIntOrString
aInt
(yStringOfIntOrString
aString
), por lo que debe definir extractores y usar encase Int(i)
lugar decase i : Int
.AGREGAR: respondí a Miles Sabin en su blog de la siguiente manera. Quizás hay varias mejoras sobre cualquiera de los dos:
size(Left(2))
osize(Right("test"))
.V
lugar deOr
, por ejemploIntVString
, `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 .
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, aD[Int ∨ String]
es asignable a (es decir, es un subtipo de) aD[Int ∨ String ∨ Double]
.Aparentemente, el compilador Scala tiene tres errores.
D[¬[Double]]
caso del partido.3)
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 probarcase _
en elget
método, para no tener que desempaquetar unOption
en elmatch
insize()
.05 de marzo de 2012: la actualización previa necesita una mejora. La solución de Miles Sabin funcionó correctamente con el subtipo.
La propuesta de mi actualización anterior (para un tipo de sindicato cercano a la primera clase) rompió el subtipo.
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 queremosA
en la posición contravariante, de modo que los supertipos deA
no sean subtipos deD[¬[A]]
niD[¬[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∨
.Entonces la solución completa es.
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.
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 unmatch
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.fuente
size
función .size
ya no aceptaD[Any]
como entrada.Hay otra forma que es un poco más fácil de entender si no asimilas a Curry-Howard:
Yo uso una técnica similar en dijon
fuente
Bueno, eso es todo muy inteligente, pero estoy bastante seguro de que ya sabe que las respuestas a sus preguntas principales son varias variedades de "No". Scala maneja la sobrecarga de manera diferente y, debe admitirse, algo menos elegante de lo que usted describe. Parte de eso se debe a la interoperabilidad de Java, parte de eso se debe a que no se quiere llegar a los casos afilados del algoritmo de inferencia de tipo, y parte de eso se debe simplemente a que simplemente no es Haskell.
fuente
Agregando a las respuestas ya excelentes aquí. Aquí hay una idea general que se basa en los tipos de unión de Miles Sabin (y las ideas de Josh) pero también los define de forma recursiva, por lo que puede tener> 2 tipos en la unión (
def foo[A : UNil Or Int Or String Or List[String]
)https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb
NB: debo agregar que después de jugar con lo anterior para un proyecto, terminé volviendo a los tipos de suma simple (es decir, rasgo sellado con subclases). Los tipos de unión de Miles Sabin son excelentes para restringir el parámetro de tipo, pero si necesita devolver un tipo de unión, entonces no ofrece mucho.
fuente
A|C <: A|B|C
problema de subtipo? stackoverflow.com/questions/45255270/… Mi instinto se siente NO porque entonces significaría queA or C
tendría que ser el subtipo de(A or B) or C
pero que no contiene el tipo,A or C
por lo que no hay esperanza de hacerA or C
un subtipo deA or B or C
al menos esta codificación ... . Qué piensas ?De los documentos , con la adición de
sealed
:Sobre la
sealed
parte:fuente