¿Cuál es el propósito de las asignaciones de tipo en Scala?
static-typing ascription (5)
Inferencia de tipo: podemos omitir el nombre explícito del tipo de algo en el código fuente, llamado Inferencia de tipo (aunque se requiere en algunos casos excepcionales).
Tipo de Asignación: Ser explícito sobre el tipo de algo se llama una Asignación de Tipo. ¿Qué diferencia puede hacer?
ex: val x = 2: Byte
también vea: 1. Podemos dar vuelta explícitamente tipo a nuestras funciones
def t1 : Option[Option[String]] = Some(None)
> t1: Option[Option[String]]
Otra forma de declarar esto podría ser:
def t2 = Some(None: Option[String])
> t2: Some[Option[String]]
Aquí no dimos la Option[Option[String]]
devolver el tipo explícitamente y el Compilador lo dedujo como Some[Option[String]]
. Por qué Some[Option[String]]
se debe a que usamos la adscripción de tipo en la definición.
Otra forma en que podemos usar la misma definición es:
def t3 = Some(None)
> t3: Some[None.type]
Esta vez No le dijimos explícitamente nada al compilador (ni esta defi). Y dedujo nuestra definición como Some [None.type]
No hay mucha información en la especificación sobre qué tipo de adscripción es, y ciertamente no hay nada allí sobre el propósito de la misma. Además de "hacer que varargs pasen", ¿para qué usaría la adscripción de tipo? A continuación se muestra algunos scala REPL para la sintaxis y los efectos de su uso.
scala> val s = "Dave"
s: java.lang.String = Dave
scala> val p = s:Object
p: java.lang.Object = Dave
scala> p.length
<console>:7: error: value length is not a member of java.lang.Object
p.length
^
scala> p.getClass
res10: java.lang.Class[_ <: java.lang.Object] = class java.lang.String
scala> s.getClass
res11: java.lang.Class[_ <: java.lang.Object] = class java.lang.String
scala> p.asInstanceOf[String].length
res9: Int = 4
La adscripción de tipos simplemente le dice al compilador qué tipo espera de una expresión, de todos los posibles tipos válidos.
Un tipo es válido si respeta las restricciones existentes, como la varianza y las declaraciones de tipo, y es uno de los tipos de expresión a la que se aplica " es un ", o hay una conversión que se aplica en el ámbito.
Entonces, java.lang.String extends java.lang.Object
, por lo tanto, cualquier String
es también un Object
. En su ejemplo, declaró que desea que la expresión s
se trate como un Object
, no como una String
. Como no hay restricciones que lo impidan y el tipo deseado es uno de los tipos s
es a , funciona.
Ahora, ¿por qué querrías eso? Considera esto:
scala> val s = "Dave"
s: java.lang.String = Dave
scala> val p = s: Object
p: java.lang.Object = Dave
scala> val ss = scala.collection.mutable.Set(s)
ss: scala.collection.mutable.Set[java.lang.String] = Set(Dave)
scala> val ps = scala.collection.mutable.Set(p)
ps: scala.collection.mutable.Set[java.lang.Object] = Set(Dave)
scala> ss += Nil
<console>:7: error: type mismatch;
found : scala.collection.immutable.Nil.type (with underlying type object Nil)
required: java.lang.String
ss += Nil
^
scala> ps += Nil
res3: ps.type = Set(List(), Dave)
También podría haber arreglado esto por tipo de escritura en la declaración ss
, o podría haber declarado que el tipo de ss
es Set[AnyRef]
.
Sin embargo, las declaraciones de tipo solo logran lo mismo siempre que asigne un valor a un identificador. Lo que uno siempre puede hacer, por supuesto, si a uno no le importa tirar el código con identificadores de un solo uso. Por ejemplo, lo siguiente no compila:
def prefixesOf(s: String) = s.foldLeft(Nil) {
case (head :: tail, char) => (head + char) :: head :: tail
case (lst, char) => char.toString :: lst
}
Pero esto hace:
def prefixesOf(s: String) = s.foldLeft(Nil: List[String]) {
case (head :: tail, char) => (head + char) :: head :: tail
case (lst, char) => char.toString :: lst
}
Sería una tontería usar un identificador aquí en lugar de Nil
. Y aunque podría simplemente escribir List[String]()
, eso no siempre es una opción. Considere esto, por ejemplo:
def firstVowel(s: String) = s.foldLeft(None: Option[Char]) {
case (None, char) => if ("aeiou" contains char.toLower) Some(char) else None
case (vowel, _) => vowel
}
Para la referencia, esto es lo que la especificación de Scala 2.7 (borrador del 15 de marzo de 2009) tiene que decir acerca de la adscripción de tipo:
Expr1 ::= ...
| PostfixExpr Ascription
Ascription ::= ‘:’ InfixType
| ‘:’ Annotation {Annotation}
| ‘:’ ‘_’ ‘*’
Puede encontrar este hilo iluminando, si es un poco complicado para seguir. Lo importante a tener en cuenta es que está agregando sugerencias de restricciones al comprobador de tipos: le da un poco más de control sobre lo que está haciendo esa fase de compilación.
Una posibilidad es cuando la red y el nivel de protocolo de serie, entonces esto:
val x = 2 : Byte
es mucho más limpio que
val x = 2.asInstanceOf[Byte]
La segunda forma también es una conversión en tiempo de ejecución (no manejada por el compilador) y podría dar lugar a algunas condiciones interesantes de exceso / defecto.
Uso la adscripción de tipo al papel sobre agujeros en la inferencia de tipo de Scala. Por ejemplo, foldLeft sobre una colección de tipo A toma un elemento inicial de tipo B y una función (B, A) => B que se usa para doblar los elementos de la colección en el elemento inicial. El valor real de tipo B se deduce del tipo del elemento inicial. Como Nil extiende List [Nothing], usarlo como elemento inicial causa problemas:
scala> val x = List(1,2,3,4)
x: List[Int] = List(1, 2, 3, 4)
scala> x.foldLeft(Nil)( (acc,elem) => elem::acc)
<console>:9: error: type mismatch;
found : List[Int]
required: scala.collection.immutable.Nil.type
x.foldLeft(Nil)( (acc,elem) => elem::acc)
^
scala> x.foldLeft(Nil:List[Int])( (acc,elem) => elem::acc )
res2: List[Int] = List(4, 3, 2, 1)
Alternativamente, puede usar List.empty [Int] en lugar de Nil: List [Int].
scala> x.foldLeft(List.empty[Int])( (acc,elem) => elem::acc )
res3: List[Int] = List(4, 3, 2, 1)
editar: List.empty [A] se implementa como
override def empty[A]: List[A] = Nil
Esta es efectivamente una forma más detallada de Nil: Lista [A]