Pergunta

Na sequência esta questão , 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 do tipo (ele compila se eu usar T ). Mas então como é que uma pessoa realmente escrever uma classe que é covariante em seu parâmetro de tipo sem recorrer a criar a coisa unparametrized ? 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] }
}

Editar - agora tenho esse baixo para o seguinte:

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

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

Como posso escrever um imutável classe Slot que é covariant em seu tipo?

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

class Slot[+T] (val some: T) { 
}
Foi útil?

Solução

Genericamente, um covariante parâmetro tipo é um que é permitido variar para baixo como a classe é subtipadas (alternativamente, variar com subtipagem, portanto, 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 é esperado um valor do tipo List[AnyVal]. Esta é realmente uma maneira muito intuitiva para os genéricos para trabalhar, mas verifica-se que ele não é sólido (quebras o sistema de tipo), quando utilizado na presença de dados mutáveis. É por isso que os genéricos são invariantes em Java. Breve exemplo de inconsistência utilizando matrizes de Java (que são erroneamente covariante):

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

Nós apenas atribuído um valor de tipo String para uma matriz do tipo Integer[]. Por razões que devem ser óbvias, esta é uma má notícia. sistema de tipo de Java realmente permite esta em tempo de compilação. O JVM vai "amavelmente" lançar uma ArrayStoreException em tempo de execução. sistema tipo impede de Scala esse problema porque o parâmetro de tipo na classe Array é invariante (declaração é [A] em vez de [+A]).

Note que existe um outro tipo de variância conhecida como contravariance . Isto é muito importante, pois explica porque covariância pode causar alguns problemas. Contravariance é literalmente o oposto de covariância: parâmetros variam para cima com subtipagem. É muito menos comum parcialmente porque é tão contra-intuitivo, embora ele não tem muito importante aplicação:. Funções

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

Observe o " - " anotação variação no parâmetro de tipo P. Esta declaração como um todo significa que Function1 é contravariant em P e covariante em R. Assim, podemos obter 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, que é o oposto para T2 e T2'. Em Inglês, isso pode ser lido como o seguinte:

Função A A é um subtipo de outra função B , se o tipo de parâmetro de A é um supertipo do tipo de parâmetro de B , enquanto o tipo de retorno de a é um subtipo do tipo de retorno de B .

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

Com o seu conhecimento recém-descoberto de co- e contravariance, você deve ser capaz de ver por exemplo a seguir não será compilado:

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

O problema é que A é covariant, enquanto a função cons espera que seu parâmetro de tipo a ser invariante . Assim, A está variando a direção errada. Curiosamente, podemos resolver este problema, tornando List contravariant em A, mas, em seguida, o tipo de retorno List[A] seria inválida como a função cons espera seu tipo de retorno para ser covariant .

Nossos apenas duas opções aqui estão a um) make A invariante, perdendo a agradável, propriedades intuitiva sub-digitação de covariância, ou b) adicionar um parâmetro de tipo local para o método cons que define A como um limite inferior:

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

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

Observe que this truque só funciona se retornar uma instância de List que é especializada no tipo B menos específica. Se você tentar fazer List mutável, as coisas quebrar desde que você acabar tentando valores Atribuir do tipo B a uma variável do tipo A, o que não é permitido pelo compilador. Sempre que você tem mutabilidade, você precisa ter um modificador de algum tipo, o que requer um parâmetro de método de um certo tipo, que (juntamente com o acessor) implica invariância. Covariance trabalha com dados imutáveis ??como a única possível operação é um assessor, que pode ser dada um tipo de retorno covariant.

Outras dicas

@ Daniel explicou isso muito bem. Mas, para explicar 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, então, lançar um erro em tempo de execução, uma vez que não teve sucesso em converter um Animal para Dog (duh!).

Em mutabilidade geral não vão bem com co-variância e contra-variância. Essa é a razão pela qual todas as coleções de Java são invariáveis.

Scala por exemplo , página 57+ para uma discussão completa deste.

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 é tipo seguro sem verificações de tempo de execução, que scala doesn' t fazer, então você está fora de sorte). Traduzindo seu exemplo de usar a 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ções / contexto para a descrição do problema e eu vou tentar novamente.

Em resposta à sua edição: ranhuras imutáveis ??são uma situação totalmente diferente ... * sorriso * Espero que o exemplo acima ajudou

.

Você precisa aplicar um limite sobre o parâmetro mais baixo. Estou tendo dificuldade em lembrar a sintaxe, mas eu acho que seria algo parecido com isto:

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

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top