Por que o exemplo não compila, também, como funciona a variação (co, contra e intra)?

Na sequência desta pergunta , alguém pode explicar o seguinte em Scala:

class Slot[+T] (var some: T) { // DOES NOT COMPILE // "COVARIANT parameter in CONTRAVARIANT position" } 

Eu entendo a distinção entre +T e T na declaração de tipo (ele compila se eu usar T ). Mas então como alguém realmente escreve uma class que é covariante em seu parâmetro de tipo sem recorrer à criação da coisa não- parametrizada ? Como posso garantir que o seguinte só pode ser criado com uma instância de T ?

 class Slot[+T] (var some: Object){ def get() = { some.asInstanceOf[T] } } 

EDIT – agora tenho isso para baixo para o seguinte:

 abstract class _Slot[+T, V <: T] (var some: V) { def getT() = { some } } 

Tudo isso é bom, mas agora eu tenho dois parâmetros de tipo, onde eu só quero um. Vou fazer a pergunta assim:

Como posso escrever uma class de Slot imutável que é covariante em seu tipo?

EDIT 2 : Duh! Eu usei var e não val . O seguinte é o que eu queria:

 class Slot[+T] (val some: T) { } 

Genericamente, um parâmetro do tipo covariante é aquele que é permitido variar conforme a class é subtipada (alternativamente, variar com a subtipagem, daí o prefixo “co-“). Mais concretamente:

 trait List[+A] 

List[Int] é um subtipo de List[AnyVal] porque Int é um subtipo de AnyVal . Isso significa que você pode fornecer uma instância de List[Int] quando um valor do tipo List[AnyVal] for esperado. Esta é realmente uma maneira muito intuitiva para os genéricos funcionarem, mas acontece que é insalubre (quebra o sistema de tipos) quando usado na presença de dados mutáveis. É por isso que os genéricos são invariantes em Java. Breve exemplo de falta de firmeza usando matrizes Java (que são erroneamente covariantes):

 Object[] arr = new Integer[1]; arr[0] = "Hello, there!"; 

Acabamos de atribuir um valor do tipo String a uma matriz do tipo Integer[] . Por razões que deveriam ser óbvias, isso é uma má notícia. O sistema de tipos do Java permite isso em tempo de compilation. A JVM “ajudará” a lançar uma ArrayStoreException em tempo de execução. O sistema de tipos do Scala evita esse problema porque o parâmetro type na class Array é invariante (a declaração é [A] vez de [+A] ).

Observe que há outro tipo de variação conhecido como contravariância . Isso é muito importante, pois explica por que a covariância pode causar alguns problemas. A contravariância é literalmente o oposto da covariância: os parâmetros variam para cima com a subtipagem. É muito menos comum em parte porque é tão contra-intuitivo, embora tenha uma aplicação muito importante: funções.

 trait Function1[-P, +R] { def apply(p: P): R } 

Observe a anotação de variância ” ” no parâmetro do tipo P Essa declaração como um todo significa que a Function1 é contravariante em P e covariante em R Assim, podemos derivar os seguintes axiomas:

 T1' <: T1 T2 <: T2' ---------------------------------------- S-Fun Function1[T1, T2] <: Function1[T1', T2'] 

Observe que T1' deve ser um subtipo (ou o mesmo tipo) de T1 , enquanto que é o oposto para T2 e T2' . Em inglês, isso pode ser lido como o seguinte:

Uma function A é um subtipo de outra function B se o tipo de parâmetro de A for um supertipo do tipo de parâmetro B enquanto o tipo de retorno de A for um subtipo do tipo de retorno de B.

A razão para essa regra é deixada como um exercício para o leitor (sugestão: pense em casos diferentes, pois as funções são subtipadas, como meu exemplo de array acima).

Com o conhecimento recém-descoberto de coexistência e contravariância, você deve ser capaz de ver por que o seguinte exemplo não compilará:

 trait List[+A] { def cons(hd: A): List[A] } 

O problema é que A é covariante, enquanto a function cons espera que seu parâmetro de tipo seja invariante . Assim, A está variando na direção errada. Curiosamente, poderíamos resolver esse problema fazendo List contravariant em A , mas o tipo de retorno List[A] seria inválido, já que a function cons espera que seu tipo de retorno seja covariante .

Nossas duas únicas opções aqui são: a) tornar A invariante, perdendo as boas e intuitivas propriedades de subdivisão de covariância, ou b) adicionar um parâmetro de tipo local ao método cons que define A como um limite inferior:

 def cons[B >: A](v: B): List[B] 

Isso agora é válido. Você pode imaginar que A está variando para baixo, mas B é capaz de variar em relação a A já que A é seu limite inferior. Com esta declaração de método, podemos ter A ser covariante e tudo funciona.

Observe que esse truque só funciona se retornarmos uma instância de List que é especializada no tipo B menos específico. Se você tentar tornar o List mutable, as coisas falharão, já que você acaba tentando atribuir valores do tipo B a uma variável do tipo A , que não é permitida pelo compilador. Sempre que você tem a mutabilidade, você precisa ter um mutador de algum tipo, o que requer um parâmetro de método de um certo tipo, que (juntamente com o acessador) implica em invariância. Covariância trabalha com dados imutáveis, já que a única operação possível é um acessador, que pode receber um tipo de retorno covariante.

@Daniel explicou isso muito bem. Mas para explicar isso em suma, se fosse permitido:

  class Slot[+T](var some: T) { def get: T = some } val slot: Slot[Dog] = new Slot[Dog](new Dog) val slot2: Slot[Animal] = slot //because of co-variance slot2.some = new Animal //legal as some is a var slot.get ?? 

slot.get irá então lançar um erro em tempo de execução, já que ele não teve sucesso em converter um Animal para Dog (duh!).

Em geral, a mutabilidade não vai bem com co-variância e contra-variância. Essa é a razão pela qual todas as collections Java são invariantes.

Veja Scala pelo exemplo , página 57+ para uma discussão completa sobre isso.

Se eu estou entendendo o seu comentário corretamente, você precisa reler a passagem começando na parte inferior da página 56 (basicamente, o que eu acho que você está pedindo não é seguro para o tipo sem verificações de tempo de execução, o que scala não faz, então você está sem sorte). Traduzindo o exemplo deles para usar sua construção:

 val x = new Slot[String]("test") // Make a slot val y: Slot[Any] = x // Ok, 'cause String is a subtype of Any y.set(new Rational(1, 2)) // Works, but now x.get() will blow up 

Se você sentir que não estou entendendo sua pergunta (uma possibilidade distinta), tente adicionar mais explicação / contexto à descrição do problema e tentarei novamente.

Em resposta à sua edição: Slots imutáveis ​​são uma situação completamente diferente … * smile * Espero que o exemplo acima tenha ajudado.

Você precisa aplicar um limite inferior no parâmetro. Estou tendo dificuldade em lembrar a syntax, mas acho que seria algo parecido com isto:

 class Slot[+T, V <: T](var some: V) { //blah } 

O Scala-por-exemplo é um pouco difícil de entender, alguns exemplos concretos teriam ajudado.