2015-11-15 5 views
23

Ich versuche zu verstehen, wie Generic funktioniert (und TypeClass auch). Das GitHub-Wiki ist sehr spärlich auf Beispiele und Dokumentation. Gibt es eine kanonische Blogpost/Dokumentationsseite, die Generic und TypeClass im Detail beschreibt?Shapeless: Generic.Aux

Konkret, was ist der Unterschied zwischen diesen beiden Methoden ?:

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

gegeben

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] 
} 

Antwort

47

Die Probleme, die bei wie Generic und TypeClass umgesetzt werden und was sie tun, sind unterschiedlich genug dass sie wahrscheinlich separate Fragen verdienen, also bleibe ich hier Generic.

Generic bietet eine Zuordnung von Fallklassen (und möglicherweise ähnlichen Typen) zu heterogenen Listen. Jede Fallklasse hat eine eindeutige hlist-Repräsentation, aber jede gegebene hlist entspricht einer sehr, sehr großen Anzahl potentieller Fallklassen. wenn wir die folgenden Fallklassen Zum Beispiel haben:

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

Die hList Darstellung zur Verfügung gestellt von Generic sowohl Foo und Bar ist Int :: String :: HNil, die für (Int, String) und andere Fallklassen auch die Darstellung ist, dass wir mit diesen beiden definieren könnte Typen in dieser Reihenfolge.

(Als Randbemerkung, LabelledGeneric ermöglicht es uns, zwischen Foo und Bar zu unterscheiden, da sie die Mitgliedsnamen in der Darstellung als Typ-Level-Strings enthält.)

Wir wollen generell in der Lage sein, den Fall zu spezifizieren Klasse und lassen Sie Shapeless die (einzigartige) generische Darstellung herausfinden, und machen Repr ein Typ Mitglied (anstelle eines Typs Parameter) ermöglicht es uns, dies ziemlich sauber zu tun. Wenn der Repräsentationstyp hlist ein Typparameter wäre, müssten Ihre find-Methoden ebenfalls einen Repr-Typparameter haben, was bedeutet, dass Sie nicht nur T angeben könnten und die Repr -Interferenz haben könnten.

Repr machen ein Typ Mitglied nur sinnvoll, weil die Repr eindeutig durch den ersten Typparameter bestimmt wird. Stellen Sie sich eine Typenklasse wie Iso[A, B] vor, die bezeugt, dass A und B isomorph sind. Diese Art Klasse ist sehr ähnlich zu Generic, aber A nicht eindeutig Dermine B -wir kann nicht einfach fragen: „Was ist der Typ, die A isomorph ist?“ - so wäre es nicht sinnvoll sein, B eine Art zu machen Mitglied (obwohl wir könnten, wenn wir wirklich wollten - Iso[A] würde einfach nicht wirklich etwas bedeuten).

Das Problem mit Typ-Mitgliedern ist, dass sie leicht zu vergessen sind, und sobald sie weg sind, sind sie für immer weg. Die Tatsache, dass der Rückgabetyp Ihres find1 nicht verfeinert ist (d. H. Den Typ member nicht enthält), bedeutet, dass die zurückgegebene Generic Instanz ziemlich nutzlos ist.Zum Beispiel kann der statische Typ von res0 auch hier Any könnte:

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 
       ^

Wenn Shapeless der Generic.materialize Makro erstellt das Generic[Foo] Beispiel fragen wir nach, ist es statisch als Generic[Foo] { type Repr = Int :: String :: HNil } getippt, so das gen Argument, dass der Compiler Hände zu find1 hat alle statischen Informationen, die wir brauchen. Das Problem ist, dass wir diesen Typ dann explizit auf einen einfachen alten unraffinierten Generic[Foo] umwandeln, und von diesem Punkt an weiß der Compiler nicht mehr, was der Repr für diese Instanz ist.

Die pfadabhängigen Typen von Scala geben uns einen Weg, die Verfeinerung nicht zu vergessen ohne Hinzufügen eines anderen Typparameters zu unserer Methode. In Ihrem find2, weiß der Compiler statisch die Repr für die eingehenden gen, so, wenn Sie sagen, dass der Rückgabetyp ist Generic[T] { type Repr = gen.Repr }, wird es in der Lage seiner Spur dieser Informationen zu behalten:

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

scala> res2.head 
res3: Int = 1 

bis Zusammengefasst: Generic hat Ein Typparameter T, der seinen Typ eindeutig bestimmt Repr, Repr ist ein Typmember anstelle eines Typparameters, so dass wir ihn nicht in alle Typensignaturen aufnehmen müssen, und pfadabhängige Typen ermöglichen dies zu verfolgen Repr, obwohl es nicht in unseren Typ-Signaturen ist.

+1

Warum nicht '' 'def find2 [T] (implizites Gen: Generic [T]): Generic.Aux [T, gen.Repr] = gen'''? – crak

+6

@crak Sie können das auch schreiben - es entspricht genau der Typverfeinerung. Ich glaube, die Leute hängen sich oft an 'Aux' auf, wenn es sich nur um eine syntaktische Annehmlichkeit handelt, also habe ich es aus dieser Antwort herausgelassen. –

+13

Travis, ich wollte nur sagen, dass ich es wirklich schätze, deine Antworten und Blog-Posts auf Shapeless zu lesen. Nontriviales Zeug sehr gut erklärt. Mach weiter! – Haspemulator

Verwandte Themen