Entendendo os Modificadores de Variância em Scala
Em Scala, os modificadores de variância controlam a relação de subtipos entre tipos genéricos. Ao definir um tipo covarianet como Lista[+T], Lista[Filho] é um subtipo de Pai. Para um tipo contravariante Lista[-T], Lista[Filho] é um supertipo de Pai.
Covariância em Scala
Considere o seguinte exemplo básico:
class Veiculo {}
class Carro extends Veiculo {}
class Recipiente[T](elemento: T) {}
val recip1: Recipiente[Carro] = new Recipiente(new Carro)
// recip1 não pode ser atribuído a recip2: Recipiente[Veiculo] sem covariância
Ao adicionar o modificador +, a covariância permite a atribuição:
class Veiculo {}
class Carro extends Veiculo {}
class Recipiente[+T](elemento: T) {}
val recip1: Recipiente[Carro] = new Recipiente(new Carro)
val recip2: Recipiente[Veiculo] = recip1 // Válido devido à covariância
Contravariância em Scala
Para ilustrar a contravariância, ajuste o exemplo anterior:
class Veiculo {}
class Carro extends Veiculo {}
class Processador[-T](item: T) {}
val proc1: Processador[Veiculo] = new Processador(new Veiculo)
val proc2: Processador[Carro] = proc1 // Válido, pois Processador[Veiculo] é subtipo de Processador[Carro]
Aqui, a definição contravariante [-T] inverte a relação de subtipos.
Limites Inferiores em Tipos Covariantes
Quando uma classe covairante contém métodos com parâmetros de tipo, pode ocorrer erros de compilação. Por exemplo:
class Veiculo {}
class Carro extends Veiculo {}
class Consumidor[+T](item: T) {
def usar(t: T) = {} // Erro: posição contravariante
}
Para resolver isso, use um limite inferior:
class Veiculo {}
class Carro extends Veiculo {}
class Consumidor[+T](item: T) {
def usar[U >: T](u: U) = { println(u) } // Correto
}
Isso ocorre porque parâmetros de método são posições contravariantes, e o limite inferior >: T permite que o método aceite supertipos de T.
Limites Superiores em Tipos Contravariantes
Em classes contravariantes, métodos que retornam o tipo parametrizado requerem um limite superior:
class Veiculo {}
class Carro extends Veiculo {}
class Processador[-T](item: T) {
def obter[U <: T](): U = { new U } // Limite superior
}
Retornos de métodos são posições covariantes, portanto, o limite superior <: T é necessário para compatiiblidade.
Exemplo Integrado de Variância
Combine covariância, contravariância e limites em um único exemplo:
class Veiculo {}
class Carro extends Veiculo {}
class Gerenciador[-S, +T]() {
def executar[U >: T](param: U): T = { new T } // Covariância com limite inferior
def processar[U <: S](dado: S): U = { new U } // Contravariância com limite superior
}
class Teste extends App {
val ger1: Gerenciador[Veiculo, Carro] = new Gerenciador()
val ger2: Gerenciador[Carro, Veiculo] = ger1 // Válido
ger2.executar(new Veiculo)
ger2.processar(new Carro)
}
Limites de Visão e Contexto
Os limites de visão (<%) permitem conversões implícitas. Exemplo:
class Ave { def cantar = {} }
class Brinquedo {}
class Verificador[T <% Ave]() {
def usar(t: T) = t.cantar
}
Ou em métodos:
class Ave { def cantar = {} }
class Brinquedo {}
class Verificador() {
def usar[T <% Ave](t: T) = t.cantar
}
class Teste extends App {
val ver = new Verificador()
ver.usar(new Brinquedo) // Requer conversão implícita
}
Para compilar, adicione uma conversão implícita:
import scala.language.implicitConversions
class Ave { def cantar = {} }
class Brinquedo {}
class Verificador() {
def usar[T <% Ave](t: T) = t.cantar
}
class Teste extends App {
implicit def brinquedoParaAve(t: Brinquedo) = new Ave
val ver = new Verificador()
ver.usar(new Brinquedo)
}
Limites de contexto, introduzidos no Scala 2.8, usam tipos parametrizados, como Ordered[A]:
def comparar[A : Ordering](x: A, y: A) = implicitly[Ordering[A]].compare(x, y)
def criarArray[A : ClassManifest](tamanho: Int) = new Array[A](tamanho)