148 votes

Scala covariance / contravariance question

A la suite de cette question, quelqu'un peut m'expliquer la suite en Scala:

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

}

Je comprends la distinction entre T+ et T dans la déclaration de type (il compile si j'utilise T). Mais alors, comment fait-on réellement écrire une classe qui est covariant dans son paramètre de type sans recourir à la création de la chose unparametrized? Comment puis-je m'assurer que les éléments suivants ne peuvent être créés avec une instance de T?

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

EDIT - reçu aujourd'hui le suivant:

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

c'est tout bon, mais j'ai maintenant deux paramètres de type, où je veux seulement un. Je vais re-poser la question ainsi:

Comment puis-je écrire un immuable Slot de la classe qui est covariant en son genre?

EDIT 2: Duh! J'ai utilisé var et pas val. Ce qui suit est ce que je voulais:

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

304voto

Daniel Spiewak Points 30706

De manière générique, un covariant paramètre de type est celui qui est autorisé à varier en bas comme la classe est sous-typés (vous pouvez aussi varier avec le sous-typage, d'où le "co-" préfixe). Plus concrètement:

trait List[+A]

List[Int] est un sous-type d' List[AnyVal] car Int est un sous-type d' AnyVal. Cela signifie que vous pouvez fournir une instance de List[Int] lorsqu'une valeur de type List[AnyVal] est attendue. C'est vraiment une façon très intuitive pour les génériques de travail, mais il s'avère que c'est malsain (pauses le type de système) lorsqu'ils sont utilisés en présence de données mutable. C'est pourquoi les génériques sont des constantes en Java. Bref exemple de troubles à l'aide de Java les tableaux (qui sont à tort covariants):

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

Nous venons de recevoir une valeur de type String d'un tableau de type Integer[]. Pour des raisons qui devraient être évidentes, ce sont de mauvaises nouvelles. Java du type de système permet effectivement présent au moment de la compilation. La JVM va "utilement" jeter un ArrayStoreException lors de l'exécution. Scala type de système évite ce problème parce que le paramètre type sur l' Array classe est invariante (déclaration est - [A] plutôt que d' [+A]).

Notez qu'il existe un autre type de variance connue comme la contravariance. Ceci est très important car il explique pourquoi la covariance peut causer quelques problèmes. La Contravariance est littéralement à l'opposé de la covariance: paramètres varient à la hausse avec le sous-typage. Il est beaucoup moins fréquent en partie parce qu'il est tellement contre-intuitif, mais il en a une très importante demande: les fonctions.

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

Notez le "-" annotation de variance sur l' P paramètre de type. Cette déclaration comme un ensemble de moyens qu' Function1 est contravariant en P et covariants en R. Ainsi, on peut obtenir les axiomes suivants:

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

Notez que T1' doit être un sous-type (ou le même type) T1, alors que c'est l'inverse pour T2 et T2'. En anglais, cela peut être lu comme suit:

Une fonction Un est un sous-type d'une autre fonction B si le paramètre de type de Un est un supertype du paramètre type de B alors que le type de retour de l' Un est un sous-type du type de retour de B.

La raison de cette règle est laissé comme exercice au lecteur (indice: une réflexion sur les différents cas que les fonctions sont sous-typés, comme mon exemple du tableau ci-dessus).

Avec votre nouvelle connaissance de la covariance et la contravariance, vous devriez être en mesure de voir pourquoi l'exemple suivant ne compile pas:

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

Le problème est qu' A est covariant, tandis que l' cons fonction attend son type de paramètre à contravariant. Ainsi, A est variable dans la mauvaise direction. Fait intéressant, nous avons pu résoudre ce problème en List contravariant en A, mais ensuite le type de retour List[A] serait nulle, comme l' cons fonction attend son type de retour covariantes.

Nos seules deux options sont a) de rendre A invariant, la perte de l'agréable, intuitive sous-typage des propriétés de la covariance, ou b) ajouter un local type de paramètre à l' cons méthode qui définit A comme limite inférieure:

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

C'est maintenant valide. Vous pouvez imaginer qu' A est variable à la baisse, mais B est capable de faire varier à la hausse à l'égard A depuis A est sa limite inférieure. Avec cette déclaration de méthode, nous pouvons avoir l' A être covariantes et tout fonctionne.

Notez que cette astuce ne fonctionne que si nous retourner une instance de List qui est spécialisé sur les moins-type B. Si vous essayez de faire de l' List mutables, les choses tomber en panne, car en fin de compte vous essayez d'affecter des valeurs de type B pour une variable de type A, ce qui est rejetée par le compilateur. Chaque fois que vous avez la mutabilité, vous devez disposer d'un mutateur, ce qui nécessite un paramètre d'une méthode, d'un certain type, qui (avec l'accesseur) implique l'invariance. La Covariance travaille avec des données immuables depuis la seule opération est un accesseur, qui peut être donné un covariant type de retour.

27voto

Jatin Points 8069

@Daniel a expliqué qu'il est très bien. Mais pour l'expliquer en bref, si c'était possible:

  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 sera alors de jeter une erreur lors de l'exécution qu'il n'a pas réussi à convertir un Animal de Dog (duh!).

En général, la mutabilité ne va pas bien avec la co-variance et de la contra-variance. C'est la raison pour laquelle tous les produits Java sont des collections de l'invariant.

7voto

MarkusQ Points 15612

Voir Scala par exemple, à la page 57+ pour une discussion complète de ce.

Si je comprends votre commentaire correctement, vous avez besoin de relire le passage commençant par le bas de la page 56 (fondamentalement, ce que je pense que vous demandez n'est pas de type sécurisé sans moment de l'exécution des contrôles, qui scala ne le fait pas, alors vous êtes hors de la chance). La traduction de leur exemple pour l'utilisation de votre construction:

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

Si vous pensez que je ne suis pas la compréhension de votre question (une possibilité distincte), essayez d'ajouter plus d'explication / contexte de la description du problème et je vais essayer de nouveau.

En réponse à votre edit: Immuable fentes sont une toute autre situation...* sourire * j'espère que l'exemple ci-dessus aidé.

3voto

Saem Points 2415

Vous avez besoin d'appliquer une limite inférieure au paramètre. Je vais avoir un moment difficile de se souvenir de la syntaxe, mais je pense que ça devrait ressembler à quelque chose comme ceci:

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

La Scala par exemple est un peu dur à comprendre, un peu d'exemples concrets aurait aidé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X