Informeless: Générique.Aux

J'essaie de comprendre comment fonctionne Generic (et TypeClass aussi). Le wiki github est très clairsemé sur les exemples et la documentation. Y a-t-il une page de blog / documentation canonique décrivant Generic et TypeClass en détail?

Concrètement, Quelle est la différence entre ces deux méthodes?:

def find1[T](implicit gen: Generic[T]): Generic[T] = gen
def find2[T](implicit gen: Generic[T]): Generic[T] { type Repr = gen.Repr } = gen

Donné

object Generic {
  type Aux[T, Repr0] = Generic[T] { type Repr = Repr0 }
  def apply[T](implicit gen: Generic[T]): Aux[T, gen.Repr] = gen
  implicit def materialize[T, R]: Aux[T, R] = macro GenericMacros.materialize[T, R]
}
23
demandé sur Juanpa 2015-11-16 01:37:30

1 réponses

Les problèmes impliqués dans la façon dont Generic et TypeClass sont implémentés et ce qu'ils font sont assez différents pour qu'ils méritent probablement des questions séparées, donc je vais m'en tenir à Generic ici.

Generic fournit un mappage des classes de cas (et des types potentiellement similaires) vers des listes hétérogènes. Toute classe de cas a une représentation hlist unique, mais toute hlist donnée correspond à un très, très grand nombre de classes de cas potentielles. Par exemple, si nous avons le cas suivant classes:

case class Foo(i: Int, s: String)
case class Bar(x: Int, y: String)

La représentation hlist fournie par Generic pour Foo et Bar est Int :: String :: HNil, qui est également la représentation pour (Int, String) et toutes les autres classes de cas que nous pourrions définir avec ces deux types dans cet ordre.

(en note de côté, LabelledGeneric nous permet de faire la distinction entre Foo et Bar, car il inclut les noms de membres dans la représentation en tant que chaînes de niveau type.)

Nous voulons généralement être en mesure de spécifier la classe de cas et laisser Informeless comprendre la représentation générique (unique), et faire Repr un membre de type (au lieu d'un paramètre de type) nous permet de le faire assez proprement. Si le type de représentation hlist était un paramètre de type, vos méthodes find devraient également avoir un paramètre de type Repr, ce qui signifie que vous ne pourriez pas spécifier uniquement le T et avoir le Repr déduit.

Faire de Repr un membre de type n'a de sens que parce que Repr est déterminé de manière unique par le premier paramètre de type. Imaginez une classe de type comme {[22] } qui témoigne que A et B sont isomorphes. Cette classe de type est très similaire à Generic, mais A ne détermine pas uniquement B-nous ne pouvons pas simplement demander " quel est le type isomorphe à A?"-il ne serait donc pas utile de faire de B un membre de type (bien que nous le puissions si nous le voulions vraiment-Iso[A] ne signifierait rien).

Le problème avec les membres de type est qu'ils sont faciles à oublier, et une fois partis, ils sont disparu à jamais. Le fait que le type de retour de votre find1 n'est pas raffiné (c'est-à-dire n'inclut pas le membre de type) signifie que l'instance Generic qu'il renvoie est à peu près inutile. Par exemple, le type statique de res0 ici pourrait aussi bien être Any:

scala> import shapeless._
import shapeless._

scala> def find1[T](implicit gen: Generic[T]): Generic[T] = gen
find1: [T](implicit gen: shapeless.Generic[T])shapeless.Generic[T]

scala> case class Foo(i: Int, s: String)
defined class Foo

scala> find1[Foo].to(Foo(1, "ABC"))
res0: shapeless.Generic[Foo]#Repr = 1 :: ABC :: HNil

scala> res0.head
<console>:15: error: value head is not a member of shapeless.Generic[Foo]#Repr
              res0.head
                   ^

Lorsque la macro Generic.materialize de Shapeless crée l'instance Generic[Foo] que nous demandons, elle est typée statiquement comme Generic[Foo] { type Repr = Int :: String :: HNil }, donc l'argument gen que le compilateur remet à find1 a toutes les informations statiques dont nous avons besoin. Le problème est que nous ensuite, convertissez explicitement ce type en un ancien Generic[Foo] non raffiné, et à partir de ce moment-là, le compilateur ne sait pas ce que Repr est pour cette instance.

Les types dépendants du chemin de Scala nous permettent de ne pas oublier le raffinement sans ajouter un autre paramètre de type à notre méthode. Dans votre find2, le compilateur connaît statiquement le Repr pour le gen entrant, donc quand vous dites que le type de retour est Generic[T] { type Repr = gen.Repr }, Il sera capable de garder une trace de cela informations:

scala> find2[Foo].to(Foo(1, "ABC"))
res2: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 1 :: ABC :: HNil

scala> res2.head
res3: Int = 1

Pour résumer: Generic a un paramètre de type {[18] } qui détermine de manière unique son membre de type Repr, Repr est un membre de type au lieu d'un paramètre de type afin que nous n'ayons pas à l'inclure dans toutes nos signatures de type, et les types dépendants du chemin rendent cela possible, nous permettant de garder une trace de Repr même si ce n'est pas dans nos signatures de type.

48
répondu Travis Brown 2015-11-16 15:27:07