Quando @uncheckedVariance é necessário no Scala e por que é usado no GenericTraversableTemplate?
-
20-09-2019 - |
Pergunta
@uncheckedVariance
pode ser usado para preencher a lacuna entre as anotações de variação do site de declaração do Scala e os genéricos invariantes do Java.
scala> import java.util.Comparator
import java.util.Comparator
scala> trait Foo[T] extends Comparator[T]
defined trait Foo
scala> trait Foo[-T] extends Comparator[T]
<console>:5: error: contravariant type T occurs in invariant position in type [-T]java.lang.Object with java.util.Comparator[T] of trait Foo
trait Foo[-T] extends Comparator[T]
^
scala> import annotation.unchecked._
import annotation.unchecked._
scala> trait Foo[-T] extends Comparator[T @uncheckedVariance]
defined trait Foo
Isso diz que java.util.Comparator é naturalmente contra-variante, que é o parâmetro de tipo T
aparece em parâmetros e nunca em um tipo de retorno.
Isso levanta a questão:por que também é usado na biblioteca de coleções Scala que não se estende das interfaces Java?
trait GenericTraversableTemplate[+A, +CC[X] <: Traversable[X]] extends HasNewBuilder[A, CC[A] @uncheckedVariance]
Quais são os usos válidos para esta anotação?
Solução
O problema é que GenericTraversableTemplate é usado duas vezes:uma vez para coleções mutáveis (onde seu parâmetro de tipo deve ser invariável) e uma vez para coleções imutáveis (onde a covariância é invariavelmente rei).
As verificações de tipo de GenericTraversableTemplate assumem covariância ou invariância para o parâmetro de tipo A.No entanto, quando herdamos uma característica mutável, temos que escolher a invariância.Por outro lado, gostaríamos de covariância em uma subclasse imutável.
Como não podemos abstrair a anotação de variação (ainda ;-)) em GenericTraversableTemplate, para que pudéssemos instanciá-la para qualquer uma delas dependendo da subclasse, temos que recorrer à conversão (@uncheckVariance é essencialmente uma conversão de tipo) .Para leitura adicional, recomendo minha dissertação (desculpe ;-)) ou nosso recente papel bitrot
Outras dicas
Na minha tese, descrevo um cálculo, Scalina, que possui anotações de limites e variância como parte da linguagem tipo (uma versão anterior também está disponível como um papel de oficina). A relevância para esta discussão é a próxima etapa que quero dar ao desenvolver este cálculo: construir outra camada sobre isso para que você possa abstrair sobre os limites (fácil) e anotações de variância (faz minha cabeça girar). Na verdade, você não apenas prenderia 1 camada extra lá, mas generalizaria suas construções de polimorfismo para que elas funcionem em todos os níveis e faça seus "atributos" (limites, anotações de variação, discussões implícitas, ...) em tipos regulares com tipos especiais, todos sujeitos a abstração.
A idéia "atributos são tipos" é explicada bem por Edsko de Vries no contexto dos tipos de singularidade.
A digitação de singularidade simplificada, Edsko de Vries, Rinus Plasmeijer e David Abrahamson. Em Olaf Chitil, Zoltán Horváth e Viktória Zsók (Eds.): IFL 2007, LNCS 5083, pp. 201-218, 2008.
Resumo: Apresentamos um sistema de tipo exclusivo que é mais simples que o sistema de singularidade da Clean e o sistema que propusemos anteriormente. O novo sistema de tipos é direto para implementar e adicionar aos compiladores existentes e pode ser facilmente estendido com recursos avançados, como tipos de classificação mais altos e imedicatividade. Descrevemos nossa implementação em Morrow, uma linguagem funcional experimental com esses dois recursos. Finalmente, provamos a solidez do sistema de tipo de núcleo em relação ao cálculo lambda de chamada por necessidade.
Encontrei outra época em que o @uncheckedVariance é usado - o método sintético que retorna o valor padrão para um parâmetro de um tipo de abstração:
M:\>scala -Xprint:typer -e "class C { def p[T >: Null](t: T = null) = t }"
[[syntax trees at end of typer]]// Scala source: (virtual file)
package <empty> {
final object Main extends java.lang.Object with ScalaObject {
def this(): object Main = {
Main.super.this();
()
};
def main(argv: Array[String]): Unit = {
val args: Array[String] = argv;
{
final class $anon extends scala.AnyRef {
def this(): anonymous class $anon = {
$anon.super.this();
()
};
class C extends java.lang.Object with ScalaObject {
<synthetic> def p$default$1[T >: Null <: Any]: Null @scala.annotation.unchecked.uncheckedVariance = null;
def this(): this.C = {
C.super.this();
()
};
def p[T >: Null <: Any](t: T = null): T = t
}
};
{
new $anon();
()
}
}
}
}