scala – Tout vs souligné dans les génériques

Quelle est la différence entre les définitions génériques suivantes dans Scala:

class Foo[T <: List[_]] 

et

 class Bar[T <: List[Any]] 

Mon instinct me dit qu’ils sont à peu près les mêmes mais que ce dernier est plus explicite. Je trouve des cas où le premier comstack mais le dernier ne le fait pas, mais je ne peux pas mettre le doigt sur la différence exacte.

Merci!

Modifier:

Puis-je en lancer un autre?

 class Baz[T <: List[_ <: Any]] 

    OK, je pensais que je devais le prendre au lieu de simplement poster des commentaires. Désolé, cela va être long si vous voulez que le TLDR passe à la fin.

    Comme le dit Randall Schulz, ici _ est un raccourci pour un type existentiel. À savoir,

     class Foo[T <: List[_]] 

    est un raccourci pour

     class Foo[T <: List[Z] forSome { type Z }] 

    Notez que contrairement à ce que mentionne la réponse de Randall Shulz (divulgation complète: je me suis également trompé dans une version antérieure de cet article, merci à Jesper Nordenberg de l'avoir signalé). Ce n'est pas la même chose que:

     class Foo[T <: List[Z]] forSome { type Z } 

    ce n'est pas la même chose que:

     class Foo[T <: List[Z forSome { type Z }] 

    Attention, il est facile de se tromper (comme le montre mon gaffe précédent): l'auteur de l'article référencé par la réponse de Randall Shulz s'est trompé (voir les commentaires) et a corrigé plus tard. Mon principal problème avec cet article est que dans l'exemple présenté, l'utilisation des existentiels est supposée nous sauver d'un problème de frappe, mais ce n'est pas le cas. Allez vérifier le code et essayez de comstackr comstackAndRun(helloWorldVM("Test")) ou comstackAndRun(intVM(42)) . Oui, ne comstack pas. Rendre comstackAndRun générique dans A rendrait le code plus facile à comstackr. En bref, ce n'est probablement pas le meilleur article sur les existentiels et leur utilité (l'auteur lui-même reconnaît dans un commentaire que l'article «doit être rangé»).

    Je recommande donc plutôt de lire cet article: http://www.artima.com/scalazine/articles/scalas_type_system.html , en particulier les sections intitulées "Types existentiels" et "Variance en Java et Scala".

    Le point important que vous devriez tirer de cet article est que les existentiels sont utiles (en plus de pouvoir traiter des classes Java génériques) lorsqu'ils traitent des types non covariants. Voici un exemple.

     case class Greets[T]( private val name: T ) { def hello() { println("Hello " + name) } def getName: T = name } 

    Cette classe est générique (notez aussi que est invariant), mais nous pouvons voir que hello ne fait pas vraiment appel au paramètre type (contrairement à getName ), donc si je reçois une instance de Greets je devrais toujours pouvoir l'appeler , quel que soit T Si je veux définir une méthode qui prend une instance de Greets et appelle simplement sa méthode hello , je pourrais essayer ceci:

     def sayHi1( g: Greets[T] ) { g.hello() } // Does not comstack 

    Bien sûr, cela ne comstack pas, car T sort de nulle part ici.

    OK alors, rendons la méthode générique:

     def sayHi2[T]( g: Greets[T] ) { g.hello() } sayHi2( Greets("John")) sayHi2( Greets('Jack)) 

    Génial, cela fonctionne. Nous pourrions aussi utiliser des existentiels ici:

     def sayHi3( g: Greets[_] ) { g.hello() } sayHi3( Greets("John")) sayHi3( Greets('Jack)) 

    Fonctionne aussi Dans l'ensemble, il n'y a pas de réel avantage à utiliser un paramètre existentiel (comme dans sayHi3 ) sur le type (comme dans sayHi2 ).

    Cependant, cela change si Greets apparaît lui-même comme paramètre de type dans une autre classe générique. Dites par exemple que nous voulons stocker plusieurs instances de Greets (avec un T différent) dans une liste. Essayons:

     val greets1: Greets[Ssortingng] = Greets("John") val greets2: Greets[Symbol] = Greets('Jack) val greetsList1: List[Greets[Any]] = List( greets1, greets2 ) // Does not comstack 

    La dernière ligne ne comstack pas parce que Greets est invariant, donc un Greets[Ssortingng] et un Greets[Symbol] ne peuvent pas être traités comme un Greets[Any] même si Ssortingng et Symbol tous deux Greets[Any] Any .

    OK, essayons avec un existentiel, en utilisant la notation abrégée _ :

     val greetsList2: List[Greets[_]] = List( greets1, greets2 ) // Comstacks fine, yeah 

    Cela comstack bien, et vous pouvez le faire, comme prévu:

     greetsSet foreach (_.hello) 

    Maintenant, rappelez-vous que la raison pour laquelle nous avons eu un problème de vérification de type en premier lieu était que Greets est invariant. Si elle était transformée en une classe covariante ( class Greets[+T] ) alors tout aurait fonctionné et nous n'aurions jamais eu besoin d'existentiels.


    Donc, pour résumer, les existentiels sont utiles pour traiter les classes invariantes génériques, mais si la classe générique n'a pas besoin d'apparaître comme paramètre de type dans une autre classe générique, il y a de fortes chances à votre méthode fonctionnera

    Revenez maintenant (enfin, je sais!) À votre question spécifique, concernant

     class Foo[T <: List[_]] 

    Parce que List est covariant, c'est à toutes fins utiles et cela revient à dire:

     class Foo[T <: List[Any]] 

    Donc, dans ce cas, l'utilisation de l'une ou l'autre notation est vraiment une question de style.

    Cependant, si vous remplacez List with Set , les choses changent:

     class Foo[T <: Set[_]] 

    Set est invariant et nous sums donc dans la même situation qu'avec la classe Greets de mon exemple. Ainsi, ce qui précède est vraiment très différent de

     class Foo[T <: Set[Any]] 

    Le premier est un raccourci pour un type existentiel lorsque le code n’a pas besoin de connaître le type ou de le contraindre:

     class Foo[T <: List[Z forSome { type Z }] 

    Ce formulaire indique que le type d'élément de List est inconnu de la class Foo plutôt que de votre deuxième formulaire, qui indique spécifiquement que le type d'élément de List est Any .

    Consultez ce bref article explicatif sur les types existentiels dans Scala.