Dans Scala, nous pouvons utiliser au moins deux méthodes pour moderniser des types existants ou nouveaux. Supposons que nous voulions exprimer que quelque chose peut être quantifié en utilisant un Int
. Nous pouvons définir le trait suivant.
Conversion implicite
trait Quantifiable{ def quantify: Int }
Et puis nous pouvons utiliser des conversions implicites pour 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 les avoir importés, nous pouvons appeler la méthode quantify
sur des chaînes et des listes. Notez que la liste quantifiable stocke sa longueur, ce qui évite la traversée coûteuse de la liste lors des appels ultérieurs à quantify
.
Classes de type
Une alternative est de définir un «témoin» Quantified[A]
qui déclare qu'un certain type A
peut être quantifié.
trait Quantified[A] { def quantify(a: A): Int }
Nous fournissons ensuite des instances de cette classe de type pour String
et List
quelque part.
implicit val stringQuantifiable = new Quantified[String] {
def quantify(s: String) = s.size
}
Et si on écrit ensuite une méthode qui a besoin de quantifier ses arguments, on écrit:
def sumQuantities[A](as: List[A])(implicit ev: Quantified[A]) =
as.map(ev.quantify).sum
Ou en utilisant la syntaxe liée au contexte:
def sumQuantities[A: Quantified](as: List[A]) =
as.map(implicitly[Quantified[A]].quantify).sum
Mais quand utiliser quelle méthode?
Vient maintenant la question. Comment puis-je choisir entre ces deux concepts?
Ce que j'ai remarqué jusqu'à présent.
classes de type
- les classes de type permettent la belle syntaxe liée au contexte
- avec les classes de type je ne crée pas de nouvel objet wrapper à chaque utilisation
- la syntaxe liée au contexte ne fonctionne plus si la classe de type a plusieurs paramètres de type; imaginez que je veux quantifier les choses non seulement avec des entiers mais avec des valeurs d'un type général
T
. Je voudrais créer une classe de typeQuantified[A,T]
conversion implicite
- puisque je crée un nouvel objet, je peux y mettre en cache des valeurs ou calculer une meilleure représentation; mais dois-je éviter cela, car cela peut se produire plusieurs fois et une conversion explicite ne serait probablement invoquée qu'une seule fois?
Ce que j'attends d'une réponse
Présentez un (ou plusieurs) cas d'utilisation dans lesquels la différence entre les deux concepts est importante et expliquez pourquoi je préférerais l'un à l'autre. Expliquer également l'essence des deux concepts et leur relation l'un avec l'autre serait bien, même sans exemple.
la source
size
d'une liste dans une valeur et dites que cela évite le parcours coûteux de la liste lors des appels ultérieurs à quantifier, mais à chaque appel à la,quantify
lelist2quantifiable
est déclenché une fois de plus réinstaurant ainsiQuantifiable
et recalculant laquantify
propriété. Ce que je dis, c'est qu'il n'y a en fait aucun moyen de mettre en cache les résultats avec des conversions implicites.Réponses:
Bien que je ne veuille pas dupliquer mon matériel de Scala In Depth , je pense qu'il vaut la peine de noter que les classes de types / traits de type sont infiniment plus flexibles.
a la capacité de rechercher dans son environnement local une classe de type par défaut. Cependant, je peux remplacer le comportement par défaut à tout moment de l'une des deux manières suivantes:
Voici un exemple:
Cela rend les classes de types infiniment plus flexibles. Une autre chose est que les classes / traits de types supportent mieux la recherche implicite .
Dans votre premier exemple, si vous utilisez une vue implicite, le compilateur effectuera une recherche implicite pour:
Qui regardera
Function1
l 'objet compagnon de et l'Int
objet compagnon.Notez que ce
Quantifiable
n'est nulle part dans la recherche implicite. Cela signifie que vous devez placer la vue implicite dans un objet de package ou l' importer dans la portée. Il est plus difficile de se souvenir de ce qui se passe.En revanche, une classe de type est explicite . Vous voyez ce qu'il recherche dans la signature de la méthode. Vous avez également une recherche implicite de
qui cherchera dans
Quantifiable
l'objet compagnon de et dansInt
l'objet compagnon de. Cela signifie que vous pouvez fournir des valeurs par défaut et de nouveaux types (comme uneMyString
classe) peuvent fournir une valeur par défaut dans leur objet compagnon et il sera recherché implicitement.En général, j'utilise des classes de types. Ils sont infiniment plus flexibles pour l'exemple initial. Le seul endroit où j'utilise des conversions implicites est lors de l'utilisation d'une couche API entre un wrapper Scala et une bibliothèque Java, et même cela peut être `` dangereux '' si vous ne faites pas attention.
la source
Un critère qui peut entrer en jeu est la façon dont vous voulez que la nouvelle fonctionnalité "se sente"; en utilisant des conversions implicites, vous pouvez donner l'impression que ce n'est qu'une autre méthode:
... tout en utilisant des classes de types, il semblera toujours que vous appelez une fonction externe:
Une chose que vous pouvez réaliser avec des classes de type et non avec des conversions implicites consiste à ajouter des propriétés à un type plutôt qu'à une instance d'un type. Vous pouvez ensuite accéder à ces propriétés même si vous ne disposez pas d'une instance du type disponible. Un exemple canonique serait:
Cet exemple montre également comment les concepts sont étroitement liés: les classes de type ne seraient pas aussi utiles s'il n'y avait pas de mécanisme pour produire une infinité de leurs instances; sans la
implicit
méthode (pas une conversion, certes), je ne pourrais avoir qu'une infinité de types possédant laDefault
propriété.la source
default
pour les futurs lecteurs.Vous pouvez penser à la différence entre les deux techniques par analogie à l'application de fonction, juste avec un wrapper nommé. Par exemple:
Une instance de la première encapsule une fonction de type
A => Int
, alors qu'une instance de la seconde a déjà été appliquée à unA
. Vous pouvez continuer le modèle ...ainsi vous pourriez penser à une
Foo1[B]
sorte de comme l'application partielle deFoo2[A, B]
à uneA
instance. Un bon exemple de ceci a été écrit par Miles Sabin sous le nom de «Dépendances fonctionnelles dans Scala» .Donc vraiment mon point est que, en principe:
la source