Qual é o objetivo das atribuições de tipo em Scala?
-
21-09-2019 - |
Pergunta
Não há muita informação nas especificações sobre o tipo de atribuição, e certamente não há nada sobre o objetivo. Além de "fazer varargs de passagem funcionar", para que eu usaria a atribuição de tipo? Abaixo está alguns Scala Repl para a sintaxe e os efeitos do 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
Solução
A atribuição de tipo está apenas dizendo ao compilador que tipo você espera de uma expressão, de todos os tipos válidos possíveis.
Um tipo é válido se respeitar as restrições existentes, como variação e declarações de tipo, e é um dos tipos da expressão a que se aplica "é um", Ou há uma conversão que se aplica ao escopo.
Então, java.lang.String extends java.lang.Object
, portanto, qualquer String
também é um Object
. No seu exemplo, você declarou que deseja a expressão s
ser tratado como um Object
, não a String
. Como não há restrições impedindo isso e o tipo desejado é um dos tipos s
é um, funciona.
Agora, por que você quer isso? Considere isto:
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)
Você também poderia ter corrigido isso por tipo de atrofação s
no ss
declaração, ou você poderia ter declarado ss
é o tipo Set[AnyRef]
.
No entanto, as declarações de tipo alcançam a mesma coisa apenas desde que você esteja atribuindo um valor a um identificador. O que sempre pode fazer, é claro, se não se preocupa em espalhar o código com identificadores de um tiro. Por exemplo, o seguinte não compila:
def prefixesOf(s: String) = s.foldLeft(Nil) {
case (head :: tail, char) => (head + char) :: head :: tail
case (lst, char) => char.toString :: lst
}
Mas isso faz:
def prefixesOf(s: String) = s.foldLeft(Nil: List[String]) {
case (head :: tail, char) => (head + char) :: head :: tail
case (lst, char) => char.toString :: lst
}
Seria bobagem usar um identificador aqui no lugar de Nil
. E embora eu pudesse apenas escrever List[String]()
Em vez disso, isso nem sempre é uma opção. Considere isso, por exemplo:
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 a referência, é isso que o Scala 2.7 Spec (15 de março de 2009) tem a dizer sobre a atribuição de tipo:
Expr1 ::= ...
| PostfixExpr Ascription
Ascription ::= ‘:’ InfixType
| ‘:’ Annotation {Annotation}
| ‘:’ ‘_’ ‘*’
Outras dicas
Uma possibilidade é quando o material de nível de rede e protocolo serial, então isso:
val x = 2 : Byte
é muito mais limpo do que
val x = 2.asInstanceOf[Byte]
O segundo formulário também é uma conversão de tempo de execução (não tratada pelo compilador) e pode levar a algumas condições interessantes de excesso/fluxo.
Eu uso a atribuição de tipo em papel sobre orifícios na inferência do tipo de Scala. Por exemplo, dobrar uma coleção do tipo A pega um elemento inicial do tipo B e uma função (b, a) => b que é usado para dobrar os elementos da coleção no elemento inicial. O valor real do tipo B é inferido do tipo do elemento inicial. Como a lista de nil estende [nada], usá -la como um 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)
Como alternativa, você pode apenas usar list.empty [int] em vez 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] é implementado como
override def empty[A]: List[A] = Nil
Esta é efetivamente uma forma mais detalhada de nulo: lista [a
Você pode encontrar este tópico Iluminante, se um pouco complicado para seguir. O importante a ser observado é que você está adicionando dicas de restrição ao verificador de tipo - ele oferece um pouco mais de controle sobre o que essa fase de compilação está fazendo.
Tipo Inferência: podemos pular explicitamente o nome de algo em código -fonte, chamado de inferência de tipo. (Embora exigido em alguns casos excepcionais.)
Tipo Atribuído: Ser explícito sobre o tipo de algo é chamado de atribuição de tipo. Que diferença ele pode fazer?
ex: val x = 2: byte
Veja também: 1. Podemos dar explicitamente o tipo de retorno às nossas funções
def t1 : Option[Option[String]] = Some(None)
> t1: Option[Option[String]]
Outra maneira de declarar isso poderia ser:
def t2 = Some(None: Option[String])
> t2: Some[Option[String]]
Aqui não damos Option[Option[String]]
Tipo de retorno explicitamente e o compilador inferiram como Some[Option[String]]
. Por que Some[Option[String]]
é porque usamos o tipo de atribuição na definição.
Outra maneira de usar a mesma definição é:
def t3 = Some(None)
> t3: Some[None.type]
Desta vez, não contamos explicitamente nada ao compilador (nem esse defi). E inferiu nossa definição como algum [nenhum.type