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 Char
s, 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é.