web-dev-qa-db-fra.com

Comment déclarer des traits comme prenant des "paramètres constructeurs" implicites?

Je conçois une hiérarchie de classes, qui se compose d'une classe de base avec plusieurs traits. La classe de base fournit des implémentations par défaut de plusieurs méthodes, et les traits remplacent sélectivement certaines méthodes via abstract override, pour agir en tant que traits/mixins empilables.

Du point de vue de la conception, cela fonctionne bien et correspond au domaine afin que je puisse ajouter une fonction de filtrage d'ici (un trait) avec un prédicat d'ici (un autre trait), etc.

Cependant, maintenant j'aimerais que certains de mes traits prennent des paramètres implicites. Je suis heureux que cela ait toujours un sens du point de vue de la conception et ne prouve pas la confusion dans la pratique. Cependant, je ne peux pas convaincre le compilateur de fonctionner avec.

Le cœur du problème semble être que je ne peux pas fournir d'arguments constructeurs pour un trait, de sorte qu'ils pourraient être marqués implicitement. Référencer le paramètre implicite dans une implémentation de méthode ne parvient pas à se compiler avec le message "Impossible de trouver la valeur implicite" attendu; J'ai essayé de "propager" l'implicite de la phase de construction (où, en pratique, il est toujours dans la portée) à être disponible dans la méthode via

implicit val e = implicitly[ClassName]

mais (comme beaucoup d'entre vous s'y attendent sans doute) cette définition a échoué avec le même message.

Il semble que le problème ici est que je ne peux pas convaincre le compilateur de marquer la signature du trait lui-même avec un implicit ClassName flag, et forcer les appelants (c'est-à-dire ceux qui mélangent le trait en un objet) à fournir l'implicite. Actuellement, mes appelants le font , mais le compilateur ne vérifie pas à ce niveau.


Existe-t-il un moyen de marquer un trait comme nécessitant que certains implicits soient disponibles au moment de la construction?

(Et sinon, est-ce tout simplement pas encore implémenté ou y a-t-il une raison plus profonde pour laquelle cela n'est pas pratique?)

38
Andrzej Doyle

En fait, j'ai souvent voulu cela auparavant, mais je viens juste de trouver cette idée. Tu peux traduire

trait T(implicit impl: ClassName) {
  def foo = ... // using impl here
}

à [MODIFIÉ: la version originale ne donnait pas accès à l'implicite pour d'autres méthodes]

trait T {
  // no need to ever use it outside T
  protected case class ClassNameW(implicit val wrapped: ClassName)

  // normally defined by caller as val implWrap = ClassNameW 
  protected val implWrap: ClassNameW 

  // will have to repeat this when you extend T and need access to the implicit
  import implWrap.wrapped

  def foo = ... // using wrapped here
}
16
Alexey Romanov

Ce n'est pas possible.

Mais vous pouvez utiliser implicitly et l'inférence de type Scala pour rendre cela aussi indolore que possible.

trait MyTrait {

    protected[this] implicit def e: ClassName

}

puis

class MyClass extends MyTrait {

    protected[this] val e = implicitly // or def

}

Succinct, et ne nécessite même pas d'écrire le type dans la classe d'extension.

14
Paul Draper

J'ai rencontré ce problème plusieurs fois, et en effet c'est un peu ennuyeux, mais pas trop. Les membres et paramètres abstraits sont généralement deux façons alternatives de faire la même chose, avec leurs avantages et leurs inconvénients; pour les traits ayant un membre abstrait n'est pas trop gênant, car il faut de toute façon une autre classe pour implémenter le trait. *

Par conséquent, vous devriez simplement avoir une déclaration de valeur abstraite dans le trait, de sorte que les classes d'implémentation doivent vous fournir un implicite. Voir l'exemple suivant - qui se compile correctement et montre deux façons de mettre en œuvre le trait donné:

trait Base[T] {
    val numT: Ordering[T]
}
/* Here we use a context bound, thus cannot specify the name of the implicit
 * and must define the field explicitly.
 */
class Der1[T: Ordering] extends Base[T] {
    val numT = implicitly[Ordering[T]]
    //Type inference cannot figure out the type parameter of implicitly in the previous line
}
/* Here we specify an implicit parameter, but add val, so that it automatically
 * implements the abstract value of the superclass.
 */
class Der2[T](implicit val numT: Ordering[T]) extends Base[T]

L'idée de base que je montre est également présente dans la réponse de Knut Arne Vedaa, mais j'ai essayé de faire un exemple plus convaincant et plus pratique, en abandonnant l'utilisation de fonctionnalités inutiles.

* Ce n'est pas la raison pour laquelle le trait ne peut pas accepter de paramètres - je ne le sais pas. Je dis simplement que la limitation est acceptable dans ce cas.

11
Blaisorblade

Comme il semble que ce ne soit pas possible, j'ai opté pour l'option de déclaration de l'implicite val sur le constructeur de la classe de base. Comme indiqué dans la question, ce n'est pas idéal, mais cela satisfait le compilateur et, de façon pragmatique, ce n'est pas trop lourd dans mon cas particulier.

Si quelqu'un a une meilleure solution, je serais heureux de l'entendre et de l'accepter.

0
Andrzej Doyle

Vous pouvez le faire comme ceci:

abstract class C

trait A { this: C =>
    val i: Int
}    

implicit val n = 3

val a = new C with A {
    val i = implicitly[Int]
}

Mais je ne sais pas s'il y a un point à cela - vous pourriez tout aussi bien référencer explicitement la valeur implicite.

Je suppose que ce que vous voulez, c'est vous débarrasser de l'implémentation de i dans l'instanciation, mais comme vous le dites vous-même, le cœur du problème est que les traits ne prennent pas de paramètres de constructeur - qu'ils soient implicites ou n'a pas d'importance.

Une solution possible à ce problème serait d'ajouter une nouvelle fonctionnalité à la syntaxe déjà valide:

trait A {
    implicit val i: Int
}

i serait implémenté par le compilateur si un implicite était dans la portée.

0
Knut Arne Vedaa