web-dev-qa-db-fra.com

Scala 2.8 tutoriel de conception de collections

Suite à ma confusion haletante , quelles sont les bonnes ressources qui expliquent comment le nouveau Scala 2.8 la bibliothèque des collections a été structurée. Je suis intéressé à trouver des informations sur la façon dont les éléments suivants s'assemblent:

  • Les classes/traits de collection eux-mêmes (par exemple List, Iterable)
  • Pourquoi les classes Like existent (par exemple TraversableLike)
  • À quoi servent les méthodes complémentaires (par exemple List.companion)
  • Comment je sais quels objets implicit sont à portée à un moment donné
73
oxbow_lakes

Préface

Il y a un 2.8 collection walk-through de Martin Odersky qui devrait probablement être votre première référence. Il a également été complété par notes architecturales , qui sera particulièrement intéressant pour ceux qui souhaitent concevoir leurs propres collections.

Le reste de cette réponse a été écrit bien avant qu'une telle chose n'existe (en fait, avant la version 2.8.0 elle-même).

Vous pouvez trouver un article à ce sujet sous la forme Scala SID # . D'autres articles dans ce domaine devraient également être intéressants pour les personnes intéressées par les différences entre Scala 2.7 et 2.8.

Je vais citer le document, de manière sélective, et compléter avec quelques réflexions. Il y a aussi quelques images, générées par Matthias sur decodified.com, et les fichiers SVG originaux peuvent être trouvés ici .

Les classes/traits de collection eux-mêmes

Il existe en fait trois hiérarchies de traits pour les collections: une pour les collections modifiables, une pour les collections immuables et une qui ne fait aucune hypothèse sur les collections.

Il existe également une distinction entre les collections parallèles, série et peut-être parallèles, introduite avec Scala 2.9. J'en parlerai dans la section suivante. La hiérarchie décrite dans cette section fait référence à exclusivement aux collections non parallèles.

L'image suivante montre la hiérarchie non spécifique introduite avec Scala 2.8: General collection hierarchy

Tous les éléments montrés sont des traits. Dans les deux autres hiérarchies, il existe également des classes héritant directement des traits ainsi que des classes qui peuvent être vues comme appartenant à cette hiérarchie par conversion implicite en classes wrapper. La légende de ces graphiques se trouve après eux.

Graphique pour une hiérarchie immuable: Immutable collection hierarchy

Graphique pour la hiérarchie mutable: Mutable collection hierarchy

Légende:

Graph legend

Voici une représentation abrégée ASCII de la hiérarchie de la collection, pour ceux qui ne peuvent pas voir les images.

                    Traversable
                         |
                         |
                      Iterable
                         |
      +------------------+--------------------+
     Map                Set                  Seq
      |                  |                    |
      |             +----+----+         +-----+------+
    Sorted Map  SortedSet   BitSet   Buffer Vector LinearSeq

Collections parallèles

Lorsque Scala 2.9 a introduit des collections parallèles, l'un des objectifs de conception était de rendre leur utilisation aussi transparente que possible. En termes plus simples, on peut remplacer une collection non parallèle (série) par une parallèle un, et en profiter instantanément.

Cependant, puisque toutes les collections étaient jusque-là en série, de nombreux algorithmes les utilisant supposaient et dépendaient du fait qu'elles l'étaient série. Les collections parallèles alimentant les méthodes avec de telles hypothèses échoueraient. Pour cette raison, toute la hiérarchie décrite dans la section précédente rend obligatoire le traitement en série.

Deux nouvelles hiérarchies ont été créées pour prendre en charge les collections parallèles.

La hiérarchie des collections parallèles porte les mêmes noms pour les traits, mais précédée de Par: ParIterable, ParSeq, ParMap et ParSet. Notez qu'il n'y a pas de ParTraversable, car toute collection prenant en charge l'accès parallèle est capable de prendre en charge le trait ParIterable plus fort. Il n'a pas non plus certains des traits les plus spécialisés présents dans la hiérarchie sérielle. Toute cette hiérarchie se trouve dans le répertoire scala.collection.parallel.

Les classes implémentant des collections parallèles diffèrent également, avec ParHashMap et ParHashSet pour les collections parallèles mutables et immuables, plus ParRange et ParVector implémentant immutable.ParSeq et ParArray implémentant mutable.ParSeq.

Il existe également une autre hiérarchie qui reflète les traits des collections série et parallèle, mais avec un préfixe Gen: GenTraversable, GenIterable, GenSeq, GenMap et GenSet. Ces traits sont parents pour les collections parallèles et en série. Cela signifie qu'une méthode prenant un Seq ne peut pas recevoir une collection parallèle, mais qu'une méthode prenant un GenSeq devrait fonctionner avec des collections en série et parallèles.

Étant donné la structure de ces hiérarchies, le code écrit pour Scala 2.8 était entièrement compatible avec Scala 2.9, et exigeait un comportement en série. Sans être réécrit, il ne peut pas prendre avantage des collections parallèles, mais les changements requis sont très faibles.

Utilisation de collections parallèles

Toute collection peut être convertie en collection parallèle en appelant la méthode par dessus. De même, toute collection peut être convertie en collection en appelant la méthode seq dessus.

Si la collecte était déjà du type demandé (parallèle ou série), aucune conversion n'aura lieu. Cependant, si l'on appelle seq sur une collection parallèle ou par sur une collection série, une nouvelle collection avec la caractéristique demandée sera générée.

Ne confondez pas seq, qui transforme une collection en une collection non parallèle, avec toSeq, qui renvoie un Seq créé à partir des éléments de la collection. L'appel de toSeq sur une collection parallèle renverra un ParSeq, pas une collection série.

Les traits principaux

Bien qu'il existe de nombreuses classes d'implémentation et sous-portraits, il existe certains traits de base dans la hiérarchie, chacun fournissant plus de méthodes ou des garanties plus spécifiques, mais réduisant le nombre de classes qui pourraient les implémenter.

Dans les sous-sections suivantes, je donnerai une brève description des principaux traits et de l'idée qui les sous-tend.

Trait TraversableOnce

Ce trait est à peu près comme le trait Traversable décrit ci-dessous, mais avec la limitation que vous ne pouvez l'utiliser que une fois. Autrement dit, toutes les méthodes appelées à un TraversableOnce mai le rendent inutilisable.

Cette limitation permet de partager les mêmes méthodes entre les collections et Iterator. Cela permet à une méthode qui fonctionne avec un Iterator mais qui n'utilise pas de méthodes spécifiques à Iterator de pouvoir réellement travailler avec n'importe quelle collection, plus les itérateurs, si elle est réécrite pour accepter TraversableOnce.

Parce que TraversableOnce unifie les collections et les itérateurs, il n'apparaît pas dans les graphiques précédents, qui ne concernent que les collections.

Trait Traversable

Au sommet de la hiérarchie se trouve le trait Traversable. Sa seule opération abstraite est

def foreach[U](f: Elem => U)

L'opération est destinée à parcourir tous les éléments de la collection et à appliquer l'opération donnée f à chaque élément. L'application est effectuée uniquement pour son effet secondaire; en fait, tout résultat de fonction de f est rejeté par foreach.

Les objets transversaux peuvent être finis ou infinis. Un exemple d'objet traversable infini est le flux de nombres naturels Stream.from(0). La méthode hasDefiniteSize indique si une collection est éventuellement infinie. Si hasDefiniteSize renvoie true, la collection est certainement finie. Si elle retourne false, la collection n'est pas encore complètement élaborée, elle peut donc être infinie ou finie.

Cette classe définit des méthodes qui peuvent être implémentées efficacement en termes de foreach (plus de 40 d'entre elles).

Trait Iterable

Ce trait déclare une méthode abstraite iterator qui renvoie un itérateur qui produit tous les éléments de la collection un par un. La méthode foreach dans Iterable est implémentée en termes de iterator. Les sous-classes de Iterable remplacent souvent foreach avec une implémentation directe pour plus d'efficacité.

La classe Iterable ajoute également des méthodes moins utilisées à Traversable, qui ne peuvent être mises en œuvre efficacement que si un iterator est disponible. Ils sont résumés ci-dessous.

xs.iterator          An iterator that yields every element in xs, in the same order as foreach traverses elements.
xs takeRight n       A collection consisting of the last n elements of xs (or, some arbitrary n elements, if no order is defined).
xs dropRight n       The rest of the collection except xs takeRight n.
xs sameElements ys   A test whether xs and ys contain the same elements in the same order

Autres traits

Après Iterable, trois traits de base en héritent: Seq, Set et Map. Tous les trois ont une méthode apply et tous les trois implémentent le trait PartialFunction, mais la signification de apply est différente dans chaque cas.

J'espère que la signification de Seq, Set et Map est intuitive. Après eux, les classes se décomposent en implémentations spécifiques qui offrent des garanties particulières en termes de performances, et les méthodes qu'elle met à disposition en conséquence. Quelques traits supplémentaires sont également disponibles, tels que LinearSeq, IndexedSeq et SortedSet.

La liste ci-dessous peut être améliorée. Laissez un commentaire avec des suggestions et je le corrigerai.

Classes de base et traits

  • Traversable - Classe de collection de base. Peut être implémenté uniquement avec foreach.
    • TraversableProxy - Proxy pour un Traversable. Pointez simplement self vers la vraie collection.
    • TraversableView - Traversable avec quelques méthodes non strictes.
    • TraversableForwarder - Transmet la plupart des méthodes à underlying, sauf toString, hashCode, equals, stringPrefix, newBuilder, view et tous les appels créant un nouvel objet itérable du même type.
    • mutable.Traversable Et immutable.Traversable - même chose que Traversable, mais en restreignant le type de collection.
    • D'autres classes de cas spéciaux Iterable, telles que MetaData, existent.
    • Iterable - Une collection pour laquelle un Iterator peut être créé (via iterator).
      • IterableProxy, IterableView, mutable et immutable.Iterable.
  • Iterator - Un trait qui n'est pas descendant de Traversable. Définissez next et hasNext.
    • CountedIterator - Un Iterator définissant count, qui renvoie les éléments vus jusqu'ici.
    • BufferedIterator - Définit head, qui renvoie l'élément suivant sans le consommer.
    • D'autres classes de cas spéciaux Iterator, telles que Source, existent.

Les cartes

  • Map - Un Iterable de Tuple2, Qui fournit également des méthodes pour récupérer une valeur (le deuxième élément du tuple) à partir d'une clé (le premier élément du tuple) . Étend également PartialFunction.
    • MapProxy - Un Proxy pour un Map.
    • DefaultMap - Un trait implémentant certaines des méthodes abstraites de Map.
    • SortedMap - Un Map dont les clés sont triées.
      • immutable.SortMap
        • immutable.TreeMap - Une classe implémentant immutable.SortedMap.
    • immutable.Map
      • immutable.MapProxy
      • immutable.HashMap - Une classe implémentant immutable.Map Via le hachage de clé.
      • immutable.IntMap - Une classe implémentant immutable.Map Spécialisée pour les clés Int. Utilise un arbre basé sur les chiffres binaires des clés.
      • immutable.ListMap - Une classe implémentant immutable.Map À travers des listes.
      • immutable.LongMap - Une classe implémentant immutable.Map Spécialisée pour les clés Long. Voir IntMap.
      • Il existe des classes supplémentaires optimisées pour un nombre spécifique d'éléments.
    • mutable.Map
      • mutable.HashMap - Une classe implémentant mutable.Map Via le hachage de clé.
      • mutable.ImmutableMapAdaptor - Une classe implémentant un mutable.Map À partir d'un immutable.Map Existant.
      • mutable.LinkedHashMap -?
      • mutable.ListMap - Une classe implémentant mutable.Map À travers des listes.
      • mutable.MultiMap - Une classe acceptant plus d'une valeur distincte pour chaque clé.
      • mutable.ObservableMap - A mixin qui, lorsqu'il est mélangé avec un Map, publie des événements aux observateurs via une interface Publisher.
      • mutable.OpenHashMap - Une classe basée sur un algorithme de hachage ouvert.
      • mutable.SynchronizedMap - A mixin qui doit être mélangé avec un Map pour en fournir une version avec des méthodes synchronisées.
      • mutable.MapProxy.

Les séquences

  • Seq - Une séquence d'éléments. On suppose une taille et une répétition d'éléments bien définies. Étend également PartialFunction.
    • IndexedSeq - Séquences prenant en charge O(1) accès aux éléments et O(1) calcul de longueur.
      • IndexedSeqView
      • immutable.PagedSeq - Une implémentation de IndexedSeq où les éléments sont produits à la demande par une fonction passée par le constructeur.
      • immutable.IndexedSeq
        • immutable.Range - Une séquence délimitée d'entiers, fermée à l'extrémité inférieure, ouverte à l'extrémité supérieure et avec une étape.
          • immutable.Range.Inclusive - Un Range a également fermé en haut de gamme.
          • immutable.Range.ByOne - Un Range dont l'étape est 1.
        • immutable.NumericRange - Une version plus générique de Range qui fonctionne avec tout Integral.
          • immutable.NumericRange.Inclusive, immutable.NumericRange.Exclusive.
          • immutable.WrappedString, immutable.RichString - Wrappers qui permet de voir un String comme un Seq[Char], Tout en conservant les méthodes String. Je ne sais pas quelle est la différence entre eux.
      • mutable.IndexedSeq
        • mutable.GenericArray - Une structure de type tableau basée sur Seq. Notez que la "classe" Array est la Array de Java, qui est plus une méthode de stockage en mémoire qu'une classe.
        • mutable.ResizableArray - Classe interne utilisée par les classes basées sur des tableaux redimensionnables.
        • mutable.PriorityQueue, mutable.SynchronizedPriorityQueue - Classes implémentant des files d'attente priorisées - files d'attente où les éléments sont retirés de la file d'attente selon un Ordering en premier, et l'ordre de mise en file d'attente en dernier.
        • mutable.PriorityQueueProxy - un résumé Proxy pour un PriorityQueue.
    • LinearSeq - Un trait pour les séquences linéaires, avec un temps efficace pour isEmpty, head et tail.
      • immutable.LinearSeq
        • immutable.List - Une implémentation de liste immuable, à lien unique.
        • immutable.Stream - Une liste paresseuse. Ses éléments ne sont calculés qu'à la demande, mais mémorisés (conservés en mémoire) par la suite. Il peut être théoriquement infini.
      • mutable.LinearSeq
        • mutable.DoublyLinkedList - Une liste avec prev, head (elem) et tail (next) mutables.
        • mutable.LinkedList - Une liste avec head (elem) et tail (next) mutables.
        • mutable.MutableList - Une classe utilisée en interne pour implémenter des classes basées sur des listes mutables.
          • mutable.Queue, mutable.QueueProxy - Une structure de données optimisée pour les opérations FIFO (Premier entré, premier sorti).
          • mutable.QueueProxy - Un Proxy pour un mutable.Queue.
    • SeqProxy, SeqView, SeqForwarder
    • immutable.Seq
      • immutable.Queue - Une classe implémentant une structure de données optimisée FIFO (First-In, First-Out). Il n'y a pas de superclasse commune des files d'attente mutable et immutable.
      • immutable.Stack - Une classe implémentant une structure de données optimisée par LIFO (Last-In, First-Out). Il n'y a pas de superclasse commune des deux piles mutableimmutable.
      • immutable.Vector -?
      • scala.xml.NodeSeq - Une classe XML spécialisée qui étend immutable.Seq.
      • immutable.IndexedSeq - Comme vu ci-dessus.
      • immutable.LinearSeq - Comme vu ci-dessus.
    • mutable.ArrayStack - Une classe implémentant une structure de données optimisée pour LIFO à l'aide de tableaux. Soi-disant nettement plus rapide qu'une pile normale.
    • mutable.Stack, mutable.SynchronizedStack - Classes implémentant une structure de données optimisée pour LIFO.
    • mutable.StackProxy - Un Proxy pour un mutable.Stack ..
    • mutable.Seq
      • mutable.Buffer - Séquence d'éléments pouvant être modifiés en ajoutant, en ajoutant ou en ajoutant de nouveaux membres.
        • mutable.ArrayBuffer - Une implémentation de la classe mutable.Buffer, Avec un temps amorti constant pour les opérations d'ajout, de mise à jour et d'accès aléatoire. Il a quelques sous-classes spécialisées, telles que NodeBuffer.
        • mutable.BufferProxy, mutable.SynchronizedBuffer.
        • mutable.ListBuffer - Un tampon soutenu par une liste. Il fournit un ajout et un ajout de temps constant, la plupart des autres opérations étant linéaires.
        • mutable.ObservableBuffer - A mixin trait qui, lorsqu'il est mélangé à un Buffer, fournit des événements de notification via une interface Publisher.
        • mutable.IndexedSeq - Comme vu ci-dessus.
        • mutable.LinearSeq - Comme vu ci-dessus.

Les décors

  • Set - Un ensemble est une collection qui comprend au plus un objet quelconque.
    • BitSet - Un ensemble d'entiers stockés sous forme de jeu de bits.
      • immutable.BitSet
      • mutable.BitSet
    • SortedSet - Un ensemble dont les éléments sont ordonnés.
      • immutable.SortedSet
        • immutable.TreeSet - Une implémentation d'un SortedSet basée sur un arbre.
    • SetProxy - Un Proxy pour un Set.
    • immutable.Set
      • immutable.HashSet - Une implémentation de Set basée sur le hachage des éléments.
      • immutable.ListSet - Une implémentation de Set basée sur des listes.
      • Des classes d'ensembles supplémentaires existent pour fournir des implémentations optimisées pour les ensembles de 0 à 4 éléments.
      • immutable.SetProxy - Un Proxy pour un Set immuable.
    • mutable.Set
      • mutable.HashSet - Une implémentation de Set basée sur le hachage des éléments.
      • mutable.ImmutableSetAdaptor - Une classe implémentant un Set mutable à partir d'un Set immuable.
      • LinkedHashSet - Une implémentation de Set basée sur des listes.
      • ObservableSet - A mixin trait qui, lorsqu'il est mélangé avec un Set, fournit des événements de notification via une interface Publisher.
      • SetProxy - Un Proxy pour un Set.
      • SynchronizedSet - A mixin trait qui, lorsqu'il est mélangé avec un Set, fournit des événements de notification via une interface Publisher.

  • Pourquoi les classes Like existent (par exemple TraversableLike)

Cela a été fait pour obtenir une réutilisation maximale du code. L'implémentation concrète générique pour les classes avec une certaine structure (un traversable, une carte, etc.) se fait dans les classes Like. Les classes destinées à la consommation générale remplacent alors les méthodes sélectionnées qui peuvent être optimisées.

  • À quoi servent les méthodes associées (par exemple, List.companion)

Le générateur pour les classes, c'est-à-dire l'objet qui sait comment créer des instances de cette classe d'une manière qui peut être utilisée par des méthodes comme map, est créé par une méthode dans l'objet compagnon. Donc, pour construire un objet de type X, je dois obtenir ce générateur de l'objet compagnon de X. Malheureusement, il n'y a aucun moyen, dans Scala, de passer de la classe X à l'objet X. À cause de cela, il y a une méthode définie dans chaque instance de X, companion, qui renvoie l'objet compagnon de la classe X.

Bien qu'une telle méthode puisse être utilisée dans des programmes normaux, sa cible est d'activer la réutilisation de code dans la bibliothèque de collection.

  • Comment je sais quels objets implicites sont à portée à un moment donné

Vous n'êtes pas censé vous en soucier. Ils sont implicites précisément pour que vous n'ayez pas besoin de comprendre comment le faire fonctionner.

Ces implications existent pour permettre aux méthodes sur les collections d'être définies sur les classes parentes mais retournent toujours une collection du même type. Par exemple, la méthode map est définie sur TraversableLike, mais si vous l'utilisez sur un List, vous obtiendrez un List.

188
Daniel C. Sobral