92 votes

Conversion implicite vs classe type

En Scala, nous pouvons utiliser au moins deux méthodes pour la rénovation existants ou de nouveaux types. Supposons que nous voulons exprimer que quelque chose peut être quantifiée à l'aide d'un Int. On peut définir les traits.

La conversion implicite

trait Quantifiable{ def quantify: Int }

Et ensuite, nous pouvons utiliser les conversions implicites à quantifier, par exemple, les Chaînes et les Listes.

implicit def string2quant(s: String) = new Quantifiable{ 
  def quantify = s.size 
}
implicit def list2quantifiable[A](l: List[A]) = new Quantifiable{ 
  val quantify = l.size 
}

Après l'importation de ces éléments, nous pouvons appeler la méthode quantify sur les chaînes et les listes. Notez que le quantifiables liste des magasins de sa longueur, de sorte qu'il évite le cher de la traversée de la liste des appels suivants à l' quantify.

Les classes de Type

Une alternative est de définir un "témoin" Quantified[A] que les états, qu'un certain type A peuvent être quantifiés.

trait Quantified[A] { def quantify(a: A): Int }

Ensuite, nous présentons des exemples de ce type de classe pour String et List quelque part.

implicit val stringQuantifiable = new Quantified[String] {
  def quantify(s: String) = s.size 
}

Et si, ensuite, on écrire une méthode qui a besoin de quantifier ses arguments, nous écrivons:

def sumQuantities[A](as: List[A])(implicit ev: Quantified[A]) = 
  as.map(ev.quantify).sum

Ou en utilisant le contexte lié à la syntaxe:

def sumQuantities[A: Quantified](as: List[A]) = 
  as.map(implicitly[Quantified[A]].quantify).sum

Mais quand à l'utilisation de la méthode?

Maintenant vient la question. Comment puis-je décider entre ces deux concepts?

Ce que j'ai remarqué jusqu'à présent.

les classes de type

  • type de classes permettent de nice contexte lié syntaxe
  • avec les classes de type je n'ai pas de créer un nouvel objet wrapper sur chaque utilisation
  • le contexte lié à la syntaxe ne fonctionne plus si le type de la classe a de multiples paramètres de type; imaginez que je veux quantifier les choses, non seulement avec des nombres entiers, mais avec des valeurs d'un type général T. Je voudrais créer une classe de type Quantified[A,T]

la conversion implicite

  • depuis que j'ai créer un nouvel objet, je peut mettre en cache les valeurs de y ou de calculer une meilleure représentation; mais dois-je éviter cela, car il peut arriver plusieurs fois et une conversion explicite serait probablement être invoqué qu'une seule fois?

Ce que j'attends d'une réponse

Présenter un (ou de plusieurs) cas d'utilisation(s) où la différence entre les deux concepts, questions et d'expliquer pourquoi je préfère l'un sur l'autre. Également expliquer l'essence des deux concepts et leurs relations les uns aux autres serait bien, même sans exemple.

41voto

jsuereth Points 3239

Alors que je ne veux pas dupliquer mon matériel de Scala En Profondeur, je pense qu'il est intéressant de noter que les classes de type/ type de traits sont infiniment plus souple.

def foo[T: TypeClass](t: T) = ...

A la recherche de son environnement local pour un défaut typeclass. Cependant, je peux remplacer le comportement par défaut à tout moment par l'une des deux façons suivantes:

  1. Création/importation d'un type implicites instance de classe dans le Champ d'application de court-circuit implicite de recherche
  2. Directement le passage d'un type de classe

Voici un exemple:

def myMethod(): Unit = {
   // overrides default implicit for Int
   implicit object MyIntFoo extends Foo[Int] { ... }
   foo(5)
   foo(6) // These all use my overridden type class
   foo(7)(new Foo[Int] { ... }) // This one needs a different configuration
}

Cela rend les classes de type infiniment plus souple. Une autre chose est que les classes de type/traits soutien implicite de recherche de mieux.

Dans ton premier exemple, si vous utilisez un accord implicite de vue, le compilateur ne implicite de recherche pour:

Function1[Int, ?]

Qui va s'intéresser à Fonction1, la compagne de l'objet et de l'Int compagnon de l'objet.

Notez que Quantifiables est nulle part dans l'implicite de recherche. Cela signifie que vous avez à la place de l'implicite vue dans un objet de package ou de l'importer dans le champ d'application. C'est plus de travail à se rappeler ce qu'il se passe.

D'autre part, un type de classe est explicite. Vous voyez ce qu'il cherche dans la signature de la méthode. Vous avez également une fonction implicite de recherche de

Quantifiable[Int]

qui va regarder dans Quantifiables du compagnon de l'objet et l' Int du compagnon de l'objet. Ce qui signifie que vous pouvez fournir des valeurs par défaut et de nouveaux types (comme un MyString classe) peut fournir une valeur par défaut à son compagnon de l'objet et il sera implicitement recherché.

En général, j'utilise des classes de type. Ils sont infiniment plus souple pour le premier exemple. Le seul endroit ou j'ai utiliser les conversions implicites, c'est quand l'assouplissement d'une couche API entre un Scala wrapper et une bibliothèque Java, et même cela peut être dangereux si vous ne faites pas attention.

20voto

Philippe Points 5372

Un critère qui peut jouer, c'est la façon dont vous voulez que la nouvelle fonctionnalité de "sentir" comme; à l'aide de conversions implicites, vous pouvez le faire paraître comme il est juste une autre méthode:

"my string".newFeature

...tout en utilisant les classes de type, il sera toujours ressembler à ça que vous appelez une fonction externe:

newFeature("my string")

Une chose que vous pouvez réaliser avec des classes de type et non pas avec les conversions implicites est l'ajout de propriétés à un type, plutôt qu'à une instance d'un type. Vous pouvez ensuite accéder à ces propriétés, même lorsque vous ne disposez pas d'une instance du type disponible. Un exemple canonique serait:

trait Default[T] { def value : T }

implicit object DefaultInt extends Default[Int] {
  def value = 42
}

implicit def listsHaveDefault[T : Default] = new Default[List[T]] {
  def value = implicitly[Default[T]].value :: Nil
}

scala> default[List[List[Int]]]
resN: List[List[Int]] = List(List(42))

Cet exemple montre également comment les concepts sont étroitement liés: les classes de type ne sera pas aussi utile si il n'y avait pas de mécanisme pour produire un nombre infini de leurs instances; sans l' implicit méthode (pas de conversion, il est vrai), je pouvais avoir finitely de nombreux types de l' Default de la propriété.

13voto

mergeconflict Points 4233

Vous pouvez penser à la différence entre les deux techniques, par analogie à la fonction de l'application, juste avec un nom de wrapper. Par exemple:

trait Foo1[A] { def foo(a: A): Int }  // analogous to A => Int
trait Foo0    { def foo: Int }        // analogous to Int

Une instance de l'ancien encapsule une fonction de type A => Int, alors qu'une instance de celle-ci a déjà été appliquée à un A. Vous pouvez continuer le motif...

trait Foo2[A, B] { def foo(a: A, b: B): Int } // sort of like A => B => Int

ainsi, on pourrait penser à de la Foo1[B] un peu comme l'application partielle de Foo2[A, B] certains A de l'instance. Un grand exemple de ceci a été écrit par Miles Sabin comme "Dépendances Fonctionnelles dans la Scala".

Si vraiment mon point est que, en principe:

  • "proxénétisme" d'une classe (par le biais de la conversion implicite) est le "zéro-ième ordre"...
  • la déclaration d'une typeclass est le "premier ordre"...
  • multi-paramètre typeclasses avec fundeps (ou quelque chose comme fundeps) est le cas général.

0voto

iain Points 4876

Cet exposé de Bill Venners (enregistrement obligatoire) de la renommée de ScalaTest explique en grande partie pourquoi et comment il a modifié ScalaTest à partir de conversions implicites en paramètres implicites (et classes de types).

Il montre les améliorations de son code et les nouvelles fonctionnalités rendues possibles par cette approche.

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