2017-09-03 2 views
3

Considérons les deux codes ci-dessous. Ils accomplissent le même objectif: que ces A[T] -s peuvent être stockés dans le ContainerT étend CExistentials vs Covariance dans Scala

Cependant, ils utilisent deux approches différentes pour atteindre cet objectif:

1) existentiaux

2) covariance Je préfère la première solution car A reste plus simple. Y a-t-il une raison pour laquelle je voudrais utiliser la seconde solution (covariance)?

Mon problème avec la deuxième solution est qu'il est pas naturel dans le sens où il ne devrait pas être la responsabilité de A pour décrire ce que je peux stocker dans un conteneur et ce pas, qui devrait être la responsabilité du conteneur. La deuxième solution est également plus compliqué une fois que je veux commencer à fonctionner sur A et ensuite je dois faire face à tout ce qui vient avec la covariance.

Quel avantage obtiendrais-je en utilisant la deuxième solution (plus compliquée, moins naturelle)?

object Existentials extends App { 

    class A[T](var t:T) 

    class C 

    class C1 extends C 

    class C2 extends C 

    class Z 

    class Container[T]{ 
    var t:T = _ 
    } 

    val c=new Container[A[_<:C]]() 
    c.t=new A(new C) 
    // c.t=new Z // not compile 

    val r: A[_ <: C] = c.t 

    println(r) 
} 

object Cov extends App{ 
    class A[+T](val t:T) 

    class C 

    class C1 extends C 

    class C2 extends C 

    class Z 

    class Container[T]{ 
    var t:T = _ 
    } 

    val c: Container[A[C]] =new Container[A[C]]() 
    c.t=new A(new C) 
    //c.t=new A(new Z) // not compile 

    val r: A[C] = c.t 

    println(r) 
} 

EDIT (en réponse à la réponse de Alexey):

Commentant: « Mon problème avec la deuxième solution est qu'il est pas naturel dans le sens où il ne devrait pas être aussi la responsabilité pour décrire ce que je peux stocker dans un conteneur et quoi que ce soit, cela devrait être la responsabilité du conteneur. "

Si je class A[T](var t:T) cela signifie que je peux stocker seulement A[T] -s et non (A[S]S<:T) dans un récipient, dans un conteneur.

Cependant si j'ai class A[+T](var t:T) puis je peux stocker A[S]S<:T ainsi dans n'importe quel récipient. Donc, lorsque je déclare A soit invariant soit covariant, je décide quel type de A [S] peut être stocké dans un conteneur (comme indiqué ci-dessus), cette décision a lieu à la déclaration A.

Cependant, je pense, cette décision devrait avoir lieu, au contraire, à la déclaration du conteneur, car il est conteneur spécifique ce qui sera autorisé à entrer dans ce conteneur, seulement A[T] -s ou aussi A[S]S<:T -s.

En d'autres termes, changer la variance A[T] a des effets globalement, tout en changeant le paramètre de type d'un conteneur A[T]-A[_<:S] a un effet local bien défini sur le conteneur lui-même. Ainsi, le principe des «changements devraient avoir des effets locaux» favorise ici la solution existentielle.

Répondre

4

Dans le premier cas A est plus simple, mais dans le second cas, ses clients sont. Comme il y a normalement plus d'un endroit où vous utilisez A, c'est souvent un compromis intéressant.Votre propre code le démontre: lorsque vous devez écrire A[_ <: C] dans le premier cas (à deux endroits), vous pouvez simplement utiliser A[C] dans le second.

En outre, dans le premier cas, vous pouvez juste écrire A[C]A[_ <: C] est vraiment désiré. Disons que vous avez une méthode

def foo(x: A[C]): C = x.t 

Maintenant, vous ne pouvez pas appeler foo(y) avec y: A[C1] même si il serait logique: y.t n'avoir le type C. Lorsque cela se produit dans votre code, il peut être réparé, mais qu'en est-il des tiers?

Bien sûr, cela vaut pour les types de bibliothèque standard ainsi: si des types comme Maybe et List ne sont pas covariantes, que ce soit des signatures pour toutes les méthodes de les prendre/retour devraient être des programmes plus complexes ou plusieurs qui sont en cours de validité et faire un sens parfait casserait.

Il ne devrait pas être de la responsabilité de A-A de décrire ce que je peux stocker dans un conteneur et ce qui ne devrait pas être la responsabilité du conteneur.

La différence ne concerne pas ce que vous pouvez stocker dans un conteneur; il s'agit de quand A[B] est un sous-type de A[C]. Cet argument est un peu comme dire que vous ne devriez pas avoir extends du tout: sinon class Apple extends Fruit vous permet de stocker un Apple dans Container[Fruit], et de décider que c'est la responsabilité de Container.

+0

Merci pour la réponse. "De plus, si vous oubliez d'utiliser l'existentiel correct dans une seule méthode, tout autre lieu qui l'appelle ne peut pas non plus utiliser le type existentiel (sans projections non sécurisées non sécurisées)." Pourriez-vous donner un exemple simple? Je ne comprends pas ce que tu veux dire. – jhegedus

+0

"Dans le premier cas, A est plus simple, mais dans le second cas, ses clients le sont." En quoi est-ce plus simple pour les clients? Je ne comprends pas, pourriez-vous s'il vous plaît donner un exemple? – jhegedus

+0

@jhegedus Voir les modifications. –