67 votes

A quoi servent les classes de types dans Scala?

Si je comprends bien ce post de blog de type "classes" de Scala est juste un "modèle", mis en œuvre avec les traits et implicites des adaptateurs.

Comme le blog dit si j'ai des traits A et un adaptateur B -> A alors que je peux appeler une fonction qui requiert l'argument de type A, avec un argument de type B sans invoquer cette carte explicitement.

Je l'ai trouvé sympa mais pas très utile. Pourriez-vous donner à un cas d'utilisation/exemple qui montre ce que cette fonctionnalité est utile pour ?

86voto

Kevin Wright Points 31665

Un cas d'utilisation, comme l'a demandé...

Imaginez que vous avez une liste de choses, pourrait être des entiers, des nombres à virgule flottante, matrices, chaînes de caractères, des formes d'onde, etc. Compte tenu de cette liste, vous voulez ajouter du contenu.

Une façon de le faire serait d'avoir quelques Addable trait de caractère qui doit être héritée par chaque type unique peut être ajouté, ou une conversion implicite d'un Addable si vous traitez avec des objets à partir d'un tiers de la bibliothèque que vous ne pouvez pas rétrofit des interfaces.

Cette approche devient rapidement écrasante quand vous voulez aussi de commencer à ajouter d'autres opérations qui peuvent être effectuées à une liste d'objets. Il ne fonctionne pas bien si vous avez besoin d'alternatives (par exemple, l'ajout de deux formes d'onde de les enchaîner, ou de les superposer?) La solution est ad-hoc polymorphisme, où vous pouvez choisir le comportement pour être adaptées à des types existants.

Pour le problème original ensuite, vous pouvez mettre en œuvre un Addable type de classe:

trait Addable[T] {
  def zero: T
  def append(a: T, b: T): T
}
//yup, it's our friend the monoid, with a different name!

Vous pouvez ensuite créer implicite sous-classé instances de ce, correspondant à chaque type que vous souhaitez faire addable:

implicit object IntIsAddable extends Addable[Int] {
  def zero = 0
  def append(a: Int, b: Int) = a + b
}

implicit object StringIsAddable extends Addable[String] {
  def zero = ""
  def append(a: String, b: String) = a + b
}

//etc...

La méthode de la somme d'une liste, puis devient facile à écrire...

def sum[T](xs: List[T])(implicit addable: Addable[T]) =
  xs.FoldLeft(addable.zero)(addable.append)

//or the same thing, using context bounds:

def sum[T : Addable](xs: List[T]) = {
  val addable = implicitly[Addable[T]]
  xs.FoldLeft(addable.zero)(addable.append)
}

La beauté de cette approche est que vous pouvez fournir une définition alternative de certains typeclass, soit le contrôle de l'implicite que vous voulez dans la portée par des importations, ou en prévoir expressément le contraire implicite de l'argument. Ainsi, il devient possible de proposer différentes façons d'ajouter des formes d'onde, ou pour spécifier l'arithmétique modulo pour entier plus. Il est aussi assez indolore pour ajouter un type de certains de la 3e partie de la bibliothèque de votre typeclass.

D'ailleurs, c'est exactement l'approche adoptée par le 2.8 collections de l'API. Si l' sum méthode est définie sur TraversableLike au lieu de List, et le type de classe est - Numeric (il contient également un peu plus d'opérations que zero et append)

33voto

Alexey Romanov Points 39124

Relisez le premier commentaire:

Une distinction cruciale entre le type de classes et d'interfaces, c'est que pour la classe A à être un "membre" d'une interface, elle doit déclarer donc sur le site de sa propre définition. En revanche, n'importe quel type peut être ajouté à un type de classe à tout moment, à condition que vous pouvez fournir les définitions, et donc les membres d'un type de classe à un moment donné dépend de la portée actuelle. Par conséquent, nous ne se soucient pas si le créateur de prévue pour le type de classe que nous voulons qu'il appartient; si non, nous permet de créer notre propre définition, en montrant qu'il n'est, en effet, appartiennent, et ensuite l'utiliser en conséquence. Donc, ce n'est pas seulement fournit une meilleure solution que de cartes, dans un certain sens, on évite tout problème de cartes étaient destinées à remédier.

Je pense que c'est l'avantage le plus important de classes de type.

Aussi, ils gèrent correctement le cas où les opérations n'ont pas l'argument du type de celui que nous envoi sur, ou avoir plus d'un. E. g. tenir compte de ce type de classe:

case class Default[T](val default: T)

object Default {
  implicit def IntDefault: Default[Int] = Default(0)

  implicit def OptionDefault[T]: Default[Option[T]] = Default(None)

  ...
}

9voto

IttayD Points 10490

Je pense à des classes de type que la possibilité d'ajouter sécurisée de type de métadonnées à une classe.

Donc, vous devez d'abord définir une classe pour modéliser le domaine du problème, puis penser à des métadonnées à ajouter. Des choses comme des Égaux, Hashable, Visible, etc. Cela crée une séparation du domaine du problème et les mécaniciens de la classe et s'ouvre sous-classement parce que la classe est plus maigre.

Sauf que, vous pouvez ajouter des classes de type de n'importe où dans la portée, et pas seulement là où la classe est définie et vous pouvez changer implémentations. Par exemple, si je fais le calcul d'un code de hachage pour une classe Point par Point à l'aide de#hashCode, puis je me suis limitée à la mise en œuvre spécifique qui ne peut pas créer une bonne distribution des valeurs pour l'ensemble spécifique de Points que j'ai. Mais si j'utilise Hashable[Point], alors je peut donner mon propre mise en œuvre.

[Mise à jour avec l'exemple] Comme un exemple, voici un cas d'utilisation, j'ai eu la semaine dernière. Dans notre produit il y a plusieurs cas de Cartes contenant des récipients en tant que valeurs. E. g., Map[Int, List[String]] ou Map[String, Set[Int]]. L'ajout de ces collections peuvent être commentée:

map += key -> (value :: map.getOrElse(key, List()))

Donc, je voulais avoir une fonction qui encapsule ce que je pourrais écrire

map +++= key -> value

Le principal problème est que les collections n'ont pas tous les mêmes méthodes pour ajouter des éléments. Certains ont '+', tandis que d'autres:+'. Je voulais aussi conserver l'efficacité de l'ajout d'éléments à une liste, de sorte que je ne voulais pas utiliser de plier/carte de créer de nouvelles collections.

La solution est d'utiliser les classes de type:

  trait Addable[C, CC] {
    def add(c: C, cc: CC) : CC
    def empty: CC
  }

  object Addable {
    implicit def listAddable[A] = new Addable[A, List[A]] {
      def empty = Nil

      def add(c: A, cc: List[A]) = c :: cc
    }

    implicit def addableAddable[A, Add](implicit cbf: CanBuildFrom[Add, A, Add]) = new Addable[A, Add] {
      def empty = cbf().result

      def add(c: A, cc: Add) = (cbf(cc) += c).result
    }
  }

Ici, j'ai défini une classe de type Addable qui peut ajouter un élément C pour une collection CC. J'ai 2 implémentations par défaut: Pour les Listes à l'aide de :: et pour les autres collections, à l'aide du générateur de cadre.

Ensuite, en utilisant ce type de classe est:

class RichCollectionMap[A, C, B[_], M[X, Y] <: collection.Map[X, Y]](map: M[A, B[C]])(implicit adder: Addable[C, B[C]]) {
    def updateSeq[That](a: A, c: C)(implicit cbf: CanBuildFrom[M[A, B[C]], (A, B[C]), That]): That  = {
      val pair = (a -> adder.add(c, map.getOrElse(a, adder.empty) ))
      (map + pair).asInstanceOf[That]
    }

    def +++[That](t: (A, C))(implicit cbf: CanBuildFrom[M[A, B[C]], (A, B[C]), That]): That  = updateSeq(t._1, t._2)(cbf)
  }

  implicit def toRichCollectionMap[A, C, B[_], M[X, Y] <: col

Le bit spécial est à l'aide de adder.add d'ajouter les éléments et adder.empty de créer de nouvelles collections pour de nouvelles clés.

Pour comparer, sans les classes de type j'aurais eu 3 options: 1. écrire une méthode par type de collection. E. g., addElementToSubList et addElementToSet etc. Cela crée beaucoup de passe-partout dans la mise en œuvre et pollue l'espace de noms 2. pour utiliser la réflexion pour déterminer si la sous-collection est une Liste de / Set. C'est délicat comme la carte est vide pour commencer (bien sûr scala permet ici aussi avec les Manifestes) 3. avoir du pauvre type de la classe en demandant à l'utilisateur pour l'alimentation de l'additionneur. Donc, quelque chose comme addToMap(map, key, value, adder), ce qui est laid

6voto

Bradford Points 1742

Encore une autre façon de trouver ce billet utile est de décrire les classes de types: Les monades ne sont pas des métaphores

Rechercher dans l'article pour classeclass. Ce devrait être le premier match. Dans cet article, l'auteur fournit un exemple de classe de types Monad.

4voto

Jonathan Warden Points 611

Une manière de regarder les classes de type est qu'ils permettent extension rétroactive ou rétroactive polymorphisme. Il ya un couple de grands postes par Casual Miracles et Daniel Westheide montrer des exemples de l'utilisation de Classes de Type de Scala pour atteindre cet objectif.

Voici un post sur mon blog qui explore différentes méthodes scala de rétroactivité supertyping, une sorte d'extension rétroactive, y compris un typeclass exemple.

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