Suite à cette question , quelqu'un peut-il expliquer ce qui suit dans 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 (elle compile si j'utilise T
). Mais alors comment écrire réellement une classe qui est covariante dans son paramètre de type sans recourir à la création de la chose non paramétrée ? Comment puis-je m'assurer que les éléments suivants ne peuvent être créés qu'avec une instance de T
?
class Slot[+T] (var some: Object){
def get() = { some.asInstanceOf[T] }
}
EDIT - maintenant cela se résume à ce qui suit:
abstract class _Slot[+T, V <: T] (var some: V) {
def getT() = { some }
}
tout va bien, mais j'ai maintenant deux paramètres de type, où je n'en veux qu'un. Je vais re-poser la question ainsi:
Comment puis-je écrire une classe immuable Slot
qui est covariante dans son type?
EDIT 2 : Duh! J'ai utilisé var
et non val
. Voici ce que je voulais:
class Slot[+T] (val some: T) {
}
generics
scala
covariance
contravariance
oxbow_lakes
la source
la source
var
c'est réglable alors queval
ne l'est pas. C'est la même raison pour laquelle les collections immuables de scala sont covariantes, mais les collections mutables ne le sont pas.Réponses:
De manière générique, un paramètre de type covariant est un paramètre qui peut varier vers le bas lorsque la classe est sous-typée (en variante, varier avec le sous-typage, d'où le préfixe "co-"). Plus concrètement:
List[Int]
est un sous-type deList[AnyVal]
parce queInt
est un sous-type deAnyVal
. Cela signifie que vous pouvez fournir une instance deList[Int]
quand une valeur de typeList[AnyVal]
est attendue. C'est vraiment un moyen très intuitif pour les génériques de fonctionner, mais il s'avère qu'il n'est pas sain (casse le système de types) lorsqu'il est utilisé en présence de données mutables. C'est pourquoi les génériques sont invariants en Java. Bref exemple de dysfonctionnement à l'aide de tableaux Java (qui sont à tort covariants):Nous venons d'attribuer une valeur de type
String
à un tableau de typeInteger[]
. Pour des raisons qui devraient être évidentes, ce sont de mauvaises nouvelles. Le système de types de Java permet en fait cela au moment de la compilation. La JVM lancera "utilement" unArrayStoreException
at runtime. Le système de type de Scala évite ce problème car le paramètre de type de laArray
classe est invariant (la déclaration est[A]
plutôt que[+A]
).Notez qu'il existe un autre type de variance appelé contravariance . Ceci est très important car cela explique pourquoi la covariance peut causer certains problèmes. La contravariance est littéralement l'opposé de la covariance: les paramètres varient à la hausse avec le sous-typage. C'est beaucoup moins courant en partie parce qu'il est tellement contre-intuitif, bien qu'il ait une application très importante: les fonctions.
Notez l' annotation de variance " - " sur le
P
paramètre de type. Cette déclaration dans son ensemble signifie qu'elleFunction1
est contravariante dansP
et covariante dansR
. Ainsi, nous pouvons dériver les axiomes suivants:Notez qu'il
T1'
doit s'agir d'un sous-type (ou du même type) deT1
, alors que c'est l'inverse pourT2
etT2'
. En anglais, cela peut se lire comme suit:La raison de cette règle est laissée comme exercice au lecteur (indice: pensez à différents cas car les fonctions sont sous-typées, comme mon exemple de tableau ci-dessus).
Avec vos nouvelles connaissances sur la co- et la contravariance, vous devriez être en mesure de voir pourquoi l'exemple suivant ne se compilera pas:
Le problème est que
A
c'est covariant, alors que lacons
fonction s'attend à ce que son paramètre de type soit invariant . Ainsi,A
fait varier la mauvaise direction. Il est intéressant de noter que nous pourrions résoudre ce problème en rendantList
contravariant dansA
, mais le type de retourList[A]
serait alors invalide car lacons
fonction s'attend à ce que son type de retour soit covariant .Nos deux seules options ici sont: a) rendre
A
invariant, en perdant les propriétés de sous-typage intuitives et intéressantes de la covariance, ou b) ajouter un paramètre de type local à lacons
méthode qui définitA
comme une limite inférieure:Ceci est maintenant valide. Vous pouvez imaginer que cela
A
varie vers le bas, maisB
est capable de varier à la hausse par rapport àA
puisqueA
c'est sa limite inférieure. Avec cette déclaration de méthode, nous pouvonsA
être covariants et tout fonctionne.Notez que cette astuce ne fonctionne que si nous retournons une instance
List
dont est spécialisée sur le type moins spécifiqueB
. Si vous essayez de rendreList
mutable, les choses échouent puisque vous finissez par essayer d'attribuer des valeurs de typeB
à une variable de typeA
, ce qui est interdit par le compilateur. Chaque fois que vous avez une mutabilité, vous devez avoir un mutateur quelconque, qui nécessite un paramètre de méthode d'un certain type, ce qui (avec l'accesseur) implique l'invariance. La covariance fonctionne avec des données immuables puisque la seule opération possible est un accesseur, auquel un type de retour covariant peut être attribué.la source
trait Animal
,trait Cow extends Animal
,def iNeedACowHerder(herder: Cow => Unit, c: Cow) = herder(c)
etdef iNeedAnAnimalHerder(herder: Animal => Unit, a: Animal) = herder(a)
. Alors, ceiNeedACowHerder({ a: Animal => println("I can herd any animal, including cows") }, new Cow {})
n'est pas grave, car notre éleveur d'animaux peut garder des vaches, maisiNeedAnAnimalHerder({ c: Cow => println("I can herd only cows, not any animal") }, new Animal {})
donne une erreur de compilation, car notre éleveur de vaches ne peut pas rassembler tous les animaux.@Daniel l'a très bien expliqué. Mais pour l'expliquer en bref, si c'était permis:
slot.get
lancera alors une erreur au moment de l'exécution car il n'a pas réussi à convertir unAnimal
enDog
(duh!).En général, la mutabilité ne va pas bien avec la co-variance et la contre-variance. C'est la raison pour laquelle toutes les collections Java sont invariantes.
la source
Voir Scala par exemple , page 57+ pour une discussion complète à ce sujet.
Si je comprends bien votre commentaire, vous devez relire le passage à partir du bas de la page 56 (en gros, ce que je pense que vous demandez n'est pas de type sécurisé sans vérification du temps d'exécution, ce que scala ne fait pas, vous n'avez donc pas de chance). Traduire leur exemple pour utiliser votre construction:
Si vous pensez que je ne comprends pas votre question (une possibilité distincte), essayez d'ajouter plus d'explications / de contexte à la description du problème et j'essaierai à nouveau.
En réponse à votre modification: les machines à sous immuables sont une situation complètement différente ... * sourire * J'espère que l'exemple ci-dessus vous a aidé.
la source
Vous devez appliquer une limite inférieure sur le paramètre. J'ai du mal à me souvenir de la syntaxe, mais je pense que cela ressemblerait à ceci:
La Scala-par-exemple est un peu difficile à comprendre, quelques exemples concrets auraient aidé.
la source