conversion - scala annotations
que es diferente entre<:<y<: en scala (4)
Definitivamente hay diferencias entre <:
y <:<
; aquí está mi intento de explicar cuál elegir.
Tomemos dos clases:
trait U
class V extends U
La restricción de tipo
<:
siempre se usa porque dirige la inferencia de tipo. Eso es lo único que puede hacer: restringir el tipo en su lado izquierdo.El tipo restringido debe referenciarse en algún lugar, generalmente en la lista de parámetros (o tipo de retorno), como en:
def whatever[A <: U](p: A): List[A] = ???
De esta forma, el compilador lanzará un error si la entrada no es una subclase de
U
, y al mismo tiempo le permitirá referirse al tipo de entrada por nombre para su uso posterior (por ejemplo, en el tipo de devolución). Tenga en cuenta que si no tiene ese segundo requisito, todo esto no es necesario (con excepciones ...), como en:def whatever(p: U): String = ??? // this will obviously only accept T <: U
La Restricción de Tipo Generalizada
<:<
por otro lado, tiene dos usos:Puede usarlo como una prueba posterior al hecho de que se dedujo algún tipo. Como en:
class List[+A] { def sum(implicit ev: A =:= Int) = ??? }
Puede crear dicha lista de cualquier tipo, pero solo se puede llamar a la
sum
cuando tiene la prueba de queA
es realmenteInt
.Puede usar la ''prueba'' anterior como una forma de inferir aún más tipos. Esto le permite inferir tipos en dos pasos en lugar de uno.
Por ejemplo, en la clase de
List
anterior, puede agregar un método deflatten
:def flatten[B](implicit ev: A <:< List[B]): List[B]
Esto no es solo una prueba, esta es una manera de agarrar ese tipo interno
B
conA
ahora arreglado .Esto también se puede usar con el mismo método: imagine que desea escribir una función de
sort
utilidad, y desea tanto el tipo de elementoT
como el tipo de colecciónColl
. Puede tener la tentación de escribir lo siguiente:def sort[T, Coll <: Seq[T]](l: Coll): Coll
Pero
T
no está obligado a contener nada: no aparece en los argumentos ni en el tipo de salida. Así queT
terminará comoNothing
, oAny
, o lo que quiera que el compilador quiera, en realidad (normalmenteNothing
). Pero con esta versión:def sort[T, Coll](l: Coll)(implicit ev: Coll <:< Seq[T]): Coll
Ahora
T
aparece en los tipos de parámetros. Habrá dos ejecuciones de inferencia (una por lista de parámetros):Coll
se deducirá de lo que se haya dado, y luego, más adelante, se buscará un implícito, y si se encuentra,T
se deducirá conColl
ahora corregido . Esto esencialmente extrae el parámetro de tipoT
delColl
deducido previamente.
Así que esencialmente, <:<
comprueba (y potencialmente infiere) los tipos como un efecto secundario de la resolución implícita, por lo que se puede utilizar en diferentes lugares / en momentos diferentes que la inferencia de tipo de parámetro. Cuando hagan lo mismo, quédate con <:
Eso ya lo se:
-
<:
es la restricción de tipo de sintaxis de Scala - mientras
<:<
es el tipo que aprovecha el Scala implícito para alcanzar el tipo de restricción
por ejemplo:
object Test {
// the function foo and bar can have the same effect
def foo[A](i:A)(implicit ev : A <:< java.io.Serializable) = i
foo(1) // compile error
foo("hi")
def bar[A <: java.io.Serializable](i:A) = i
bar(1) // compile error
bar("hi")
}
pero quiero saber cuándo necesitamos usar <:
y <:<
?
y si ya tenemos <:
¿por qué necesitamos <:<
?
¡Gracias!
Después de pensar un poco, creo que tiene algo diferente. por ejemplo:
object TestAgain {
class Test[A](a: A) {
def foo[A <: AnyRef] = a
def bar(implicit ev: A <:< AnyRef) = a
}
val test = new Test(1)
test.foo // return 1
test.bar // error: Cannot prove that Int <:< AnyRef.
}
este menas:
- el alcance de
<:
está solo en el método param genérico tpye scopefoo[A <: AnyRef]
. En el ejemplo, el métodofoo
tiene su tpye genéricoA
, pero no elA
en claseTest[A]
- el alcance de
<:<
, primero encontrará el tipo genérico del método, pero labar
método no tendrá el tipo genérico param, por lo que encontrará el tipo genérico deTest[A]
.
entonces, creo que es la principal diferencia.
La diferencia principal entre los dos es que <:
es una restricción en el tipo, mientras que <:<
es un tipo para el cual el compilador tiene que encontrar evidencia, cuando se usa como un parámetro implícito. Lo que eso significa para nuestro programa es que en el <:
caso, el tipo inferencer intentará encontrar un tipo que satisfaga esta restricción. P.ej
def foo[A, B <: A](a: A, b: B) = (a,b)
scala> foo(1, List(1,2,3))
res1: (Any, List[Int]) = (1,List(1, 2, 3))
Aquí el inferencer encuentra que Int
y List[Int]
tienen el tipo super común Any
, por lo que infiere que para A
satisface B <: A
<:<
es más restrictivo, porque el tipo inferencer se ejecuta antes que la resolución implícita. Entonces los tipos ya están corregidos cuando el compilador intenta encontrar la evidencia. P.ej
def bar[A,B](a: A, b: B)(implicit ev: B <:< A) = (a,b)
scala> bar(1,1)
res2: (Int, Int) = (1,1)
scala> bar(1,List(1,2,3))
<console>:9: error: Cannot prove that List[Int] <:< Int.
bar(1,List(1,2,3))
^
1. def bar[A <: java.io.Serializable](i:A) = i
<: - garantiza que la instancia de i del tipo parámetro A será subtipo de Serializable
2. def foo[A](i:A)(implicit ev : A <:< java.io.Serializable) = i
<: <- garantiza que el contexto de ejecución contendrá un valor implícito (para ev paramenter) de tipo A, que es el subtipo de Serializable . Esto implícito se define en Predef.scala y para el método foo y se comprueba si la instancia del tipo parámetro A es subtipo de Serializable :
implicit def conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]
caso ficticio de usar el operador <: <:
class Boo[A](x: A) {
def get: A = x
def div(implicit ev : A <:< Double) = x / 2
def inc(implicit ev : A <:< Int) = x + 1
}
val a = new Boo("hi")
a.get // - OK
a.div // - compile time error String not subtype of Double
a.inc // - compile tile error String not subtype of Int
val b = new Boo(10.0)
b.get // - OK
b.div // - OK
b.inc // - compile time error Double not subtype of Int
val c = new Boo(10)
c.get // - OK
c.div // - compile time error Int not subtype of Double
c.inc // - OK
- si no llamamos a los métodos qué no cumple <: <condición que todos compilan y ejecutan.