Após essa 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 classe que é covariante em seu parâmetro de tipo sem recorrer a criar a coisa não parametrizada ? Como posso garantir que o seguinte possa ser criado apenas com uma instância de T
?
class Slot[+T] (var some: Object){
def get() = { some.asInstanceOf[T] }
}
EDIT - agora reduzimos para o seguinte:
abstract class _Slot[+T, V <: T] (var some: V) {
def getT() = { some }
}
isso é tudo de bom, mas agora tenho dois parâmetros de tipo, onde quero apenas um. Vou re-perguntar a pergunta assim:
Como posso escrever uma classe imutável Slot
que seja covariante em seu tipo?
EDIT 2 : Duh! Eu usei var
e não val
. A seguir, é o que eu queria:
class Slot[+T] (val some: T) {
}
generics
scala
covariance
contravariance
oxbow_lakes
fonte
fonte
var
é configurável enquantoval
não é. É a mesma razão pela qual as coleções imutáveis do scala são covariantes, mas as mutáveis não.Respostas:
Genericamente, um parâmetro do tipo covariante é aquele que pode variar conforme a classe é subtipada (alternativamente, varia com a subtipagem, daí o prefixo "co-"). Mais concretamente:
List[Int]
é um subtipo deList[AnyVal]
porqueInt
é um subtipo deAnyVal
. Isso significa que você pode fornecer uma instância deList[Int]
quando um valor do tipoList[AnyVal]
é esperado. Essa é realmente uma maneira muito intuitiva para os genéricos funcionarem, mas acontece que é incorreto (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 insatisfação usando matrizes Java (erroneamente covariantes):Acabamos de atribuir um valor do tipo
String
a uma matriz do tipoInteger[]
. Por razões que deveriam ser óbvias, são más notícias. O sistema de tipos Java realmente permite isso em tempo de compilação. A JVM lançará um "útil" umArrayStoreException
em tempo de execução. O sistema de tipos do Scala evita esse problema porque o parâmetro de tipo naArray
classe é invariável (a declaração é[A]
melhor que[+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. Contravariância é literalmente o oposto de covariância: os parâmetros variam para cima com a subtipagem. É muito menos comum parcialmente porque é muito contra-intuitivo, embora tenha uma aplicação muito importante: funções.
Observe a anotação de variação " - " no
P
parâmetro type. Esta declaração como um todo significa queFunction1
é contravarianteP
e covariante emR
. Assim, podemos derivar os seguintes axiomas:Observe que
T1'
deve ser um subtipo (ou o mesmo tipo) deT1
, enquanto que é o oposto deT2
eT2'
. Em inglês, isso pode ser lido da seguinte maneira:O motivo desta regra é deixado como um exercício para o leitor (dica: pense em casos diferentes, pois as funções são subtipadas, como meu exemplo de matriz acima).
Com seu conhecimento recém-encontrado de co- e contravariância, você poderá ver por que o exemplo a seguir não será compilado:
O problema é que
A
é covariante, enquanto acons
função espera que seu parâmetro de tipo seja invariável . Assim,A
está variando na direção errada. Curiosamente, poderíamos resolver esse problema tornandoList
contravarianteA
, mas o tipo de retornoList[A]
seria inválido, pois acons
função espera que seu tipo de retorno seja covariante .Nossas únicas duas opções aqui são: a)
A
invariável, perdendo as boas e intuitivas propriedades de sub-digitação da covariância, ou b) adicionar um parâmetro de tipo local aocons
método que defineA
como um limite inferior:Isso agora é válido. Você pode imaginar que
A
está variando para baixo, masB
é capaz de variar para cima em relação aA
uma vez queA
é seu limite inferior. Com esta declaração de método, podemosA
ser covariantes e tudo dá certo.Observe que esse truque só funciona se retornarmos uma instância
List
especializada no tipo menos específicoB
. Se você tentar tornarList
mutável, as coisas se deterioram, pois você acaba tentando atribuir valores do tipoB
a uma variável do tipoA
, o que não é permitido pelo compilador. Sempre que você tem mutabilidade, é necessário ter algum tipo de mutador, o que requer um parâmetro de método de um determinado tipo, o qual (junto com o acessador) implica invariância. A covariância trabalha com dados imutáveis, pois a única operação possível é um acessador, que pode receber um tipo de retorno covariante.fonte
trait Animal
,trait Cow extends Animal
,def iNeedACowHerder(herder: Cow => Unit, c: Cow) = herder(c)
edef iNeedAnAnimalHerder(herder: Animal => Unit, a: Animal) = herder(a)
. Então,iNeedACowHerder({ a: Animal => println("I can herd any animal, including cows") }, new Cow {})
tudo bem, pois nosso pastor de animais pode agrupar vacas, masiNeedAnAnimalHerder({ c: Cow => println("I can herd only cows, not any animal") }, new Animal {})
gera um erro de compilação, pois nosso pastor de vacas não pode agrupar todos os animais.@ Daniel explicou muito bem. Mas, para explicar em resumo, se for permitido:
slot.get
em seguida, lançará um erro no tempo de execução, pois não obteve êxito na conversão de umAnimal
paraDog
(duh!).Em geral, a mutabilidade não combina bem com co-variação e contra-variação. Essa é a razão pela qual todas as coleções Java são invariantes.
fonte
Veja Scala por exemplo , página 57+ para uma discussão completa sobre isso.
Se estou entendendo o seu comentário corretamente, você precisa reler a passagem que começa 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 o scala não faz, então você está sem sorte). Traduzindo o exemplo deles para usar sua construção:
Se você acha que não estou entendendo sua pergunta (uma possibilidade distinta), tente adicionar mais explicações / contexto à descrição do problema e tentarei novamente.
Em resposta à sua edição: slots imutáveis são uma situação totalmente diferente ... * smile * Espero que o exemplo acima tenha ajudado.
fonte
Você precisa aplicar um limite inferior ao parâmetro. Estou tendo dificuldade em lembrar a sintaxe, mas acho que seria algo como isto:
O Scala-by-example é um pouco difícil de entender, alguns exemplos concretos teriam ajudado.
fonte