60 votes

Comment Scala collections en mesure de retourner le bon type de collection à partir d'une carte de l'opération?

Note: Ceci est une FAQ, a demandé spécifiquement si je peux répondre moi-même, en tant que ce problème semble venir assez souvent et je veux le mettre dans un endroit où il peut (je l'espère) être facilement trouvé via une recherche

Comme demandé par un commentaire sur ma réponse ici


Par exemple:

"abcde" map {_.toUpperCase} //returns a String
"abcde" map {_.toInt} // returns an IndexedSeq[Int]
BitSet(1,2,3,4) map {2*} // returns a BitSet
BitSet(1,2,3,4) map {_.toString} // returns a Set[String]

En regardant dans le scaladoc, tous ces utiliser l' map fonctionnement hérité de l' TraversableLike, alors comment se fait il est toujours en mesure de retourner la plus correcte de la collection? Même String, ce qui fournit map via une conversion implicite.

79voto

Kevin Wright Points 31665

Scala collections sont des choses intelligentes...

Internes de la collection de la bibliothèque est l'un des sujets plus avancés dans les terres de la Scala. Elle implique plus-kinded types, l'inférence, la variance, implicites, et l' CanBuildFrom mécanisme afin de le rendre incroyablement générique, facile à utiliser et puissant à partir d'un utilisateur face à la perspective. La compréhension du point de vue d'une API designer n'est pas un léger tâche d'être pris par un débutant.

D'autre part, il est extrêmement rare que vous aurez jamais besoin de travailler avec des collections à cette profondeur.

Donc, nous allons commencer...

Avec la sortie de la Scala 2.8, la collection de la bibliothèque a été entièrement réécrit pour éviter les doublons, d'un grand nombre de méthodes ont été déplacés vers un seul endroit, de sorte que l'entretien continu et l'ajout de nouvelles méthodes de collecte serait beaucoup plus facile, mais il fait aussi de la hiérarchie plus difficile à comprendre.

Prendre en List par exemple, ce hérite de (à son tour)

  • LinearSeqOptimised
  • GenericTraversableTemplate
  • LinearSeq
  • Seq
  • SeqLike
  • Iterable
  • IterableLike
  • Traversable
  • TraversableLike
  • TraversableOnce

C'est tout à fait une poignée! Alors, pourquoi cette hiérarchie profonde? Ignorant l' XxxLike traits brièvement, chaque niveau de la hiérarchie ajoute un peu de fonctionnalités, ou une plus version optimisée de l'héritage de fonctionnalités (par exemple, l'extraction d'un élément à l'index, Traversable nécessite une combinaison de drop et head des opérations, d'une inefficacité flagrante sur une séquence indexée). Si possible, toutes les fonctionnalité est poussé aussi loin que la hiérarchie comme il peut éventuellement aller, en maximisant le nombre de sous-classes qui peuvent l'utiliser et de l'élimination du double emploi.

map est juste un exemple. La méthode est implémentée en TraversableLike (Si l' XxxLike traits seulement existent vraiment pour les concepteurs de la bibliothèque, il est donc généralement considérée comme une méthode d' Traversable pour la plupart des fins utiles - j'en viens à la partie peu de temps), et est largement hérité. Il est possible de définir une version optimisée dans certains sous-classe, mais il doit encore se conformer à la même signature. Envisager les utilisations suivantes d' map (comme mentionné dans la question):

"abcde" map {_.toUpperCase} //returns a String
"abcde" map {_.toInt} // returns an IndexedSeq[Int]
BitSet(1,2,3,4) map {2*} // returns a BitSet
BitSet(1,2,3,4) map {_.toString} // returns a Set[String]

Dans chaque cas, la sortie est du même type que l'entrée dans la mesure du possible. Quand il n'est pas possible, des super-classes de la type d'entrée sont vérifiés jusqu'à ce que l'on est trouvé que le fait d'offrir un type de retour valide. L'obtention de ce droit a fallu beaucoup de travail, surtout quand on considère qu' String n'est même pas une collection, c'est juste implicitement convertible.

Alors, comment est-il fait?

La moitié de l'énigme est l' XxxLike traits (je ne dis j'aimerais obtenir pour eux...), dont la fonction principale est de prendre un Repr type de param (abréviation de "Représentation") afin qu'ils vous connaissent le vrai sous-classe fait d'être opéré. Donc, par exemple, TraversableLike est le même que Traversable, mais prélevée sur l' Repr type de param. Ce param est ensuite utilisé par la deuxième moitié de l'énigme; l' CanBuildFrom type de classe qui capture source type de collection, élément-cible type et la cible type de collection pour être utilisé par la collection de la transformation des opérations.

Il est plus facile à expliquer avec un exemple!

BitSet définit une instance implicite de CanBuildFrom comme ceci:

implicit def canBuildFrom: CanBuildFrom[BitSet, Int, BitSet] = bitsetCanBuildFrom

Lors de la compilation d' BitSet(1,2,3,4) map {2*}, le compilateur va tenter implicite de recherche d' CanBuildFrom[BitSet, Int, T]

C'est l'astuce... Il n'y a qu'un implicite dans le champ qui correspond à la première des deux paramètres de type. Le premier paramètre est Repr, comme capturé par l' XxxLike de trait, et le second est le type d'élément, tel que présenté par la collection actuelle trait (par exemple, Traversable). L' map de l'opération est alors également paramétré avec un type, ce type T est déduit basé sur le troisième paramètre de type de l' CanBuildFrom instance qui a été implicitement situé. BitSet dans ce cas.

Si les deux premiers paramètres de type d' CanBuildFrom sont des entrées, être utilisé pour les implicites de recherche, et le troisième paramètre est une sortie, à être utilisé pour l'inférence.

CanBuildFrom en BitSet correspond dès lors les deux types d' BitSet et Int, de sorte que la recherche va réussir, et en a déduit le type de retour sera également BitSet.

Lors de la compilation d' BitSet(1,2,3,4) map {_.toString}, le compilateur va tenter implicite de recherche d' CanBuildFrom[BitSet, String, T]. On va à l'échec de l'implicite dans BitSet, de sorte que le compilateur va essayer sa super-classe - Set - Ce qui contient de l'implicite:

implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Set[A]] = setCanBuildFrom[A]

Ce qui correspond, en raison Coll est un alias de type qui est initialisé à être BitSet lorsque BitSet provient Set. L' A correspondra à rien, comme l' canBuildFrom est paramétré avec le type A, dans ce cas, il est déduit d' String... ce qui donne un type de retour d' Set[String].

Donc, pour mettre correctement en œuvre un type de collection, vous avez non seulement besoin de fournir un bon implicite de type CanBuildFrom, mais vous devez également vous assurer que le type concret de cette de cette collection est fournie en tant qu' Repr param à la bonne mère traits (par exemple, ce serait MapLike dans le cas de sous-classement d' Map).

String est un peu plus compliqué car il fournit map par une conversion implicite. La conversion implicite est-à - StringOps, ce qui sous-classes StringLike[String], ce qui en fin de compte dérive TraversableLike[Char,String] - String étant l' Repr type de param.

Il y a aussi un CanBuildFrom[String,Char,String] dans le champ d'application de sorte que le compilateur sait que lors du mappage des éléments d'un String de Chars, puis le type de retour doit également être une chaîne de caractères. A partir de ce point, le même mécanisme est utilisé.

8voto

huynhjl Points 26045

L' Architecture de la Scala de Collections de pages en ligne ont une explication détaillée orientée vers les aspects pratiques de la création de nouvelles collections basées sur la 2.8 collection design.

Citation:

"Ce qui doit être fait, si vous souhaitez intégrer une nouvelle classe de collection, de sorte qu'il peut tirer profit de toutes les opérations prévues au droit des types? Sur les prochaines pages, vous serez guidé à travers deux exemples qui font cela."

Il utilise comme exemple une collection pour le codage de séquences d'ARN et une pour Patricia trie. Regardez pour les Traiter avec la carte et les amis de la section pour l'explication de ce qui à faire pour retourner la collecte approprié type.

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