2015-12-31 16 views
40

Ich bin mir ziemlich sicher, dass ich hier etwas vermisse, da ich Shapeless ziemlich neu bin und ich lerne, aber wann ist die Aux-Technik tatsächlich erforderlich? Ich sehe, dass es verwendet wird, um eine type Erklärung zu entlarven, indem es in die Unterschrift einer anderen "Begleiter" type Definition angehoben wird.Warum ist die Aux-Technik für Berechnungen auf Typenebene erforderlich?

aber ist das nicht fast gleichzusetzen mit R in die Typ-Signatur von F?

trait F[A,R] { def value: R } 
implicit def fint = new F[Int,Long] { val value = 1L } 
implicit def ffloat = new F[Float,Double] { val value = 2.0D } 
def f[T,R](t:T)(implicit f: F[T,R]): R = f.value 
f(100) // res4: Long = 1L 
f(100.0f) // res5: Double = 2.0 

Ich sehe, dass pfadabhängigen Typ würde Vorteile bringen, wenn man sie in Argumentlisten verwenden können, aber wir wissen, dass wir nicht so

def g[T](t:T)(implicit f: F[T], r: Blah[f.R]) ... 

tun können, sind wir immer noch gezwungen, Setzen Sie einen zusätzlichen Typparameter in die Signatur g. Durch die Verwendung der Aux Technik, sind wir auch erforderlich, um zusätzliche Zeit mit dem Schreiben der Begleiter object verbringen. Aus der Sicht der Benutzung würde es für einen naiven Benutzer wie mich aussehen, dass es überhaupt keinen Vorteil gibt, pfadabhängige Typen zu verwenden.

Es gibt nur einen Fall, den ich mir vorstellen kann, dh für eine bestimmte Berechnung auf Typenebene wird mehr als ein Ergebnis auf Textebene zurückgegeben, und Sie möchten möglicherweise nur einen davon verwenden.

Ich denke, alles läuft darauf hinaus, dass ich in meinem einfachen Beispiel etwas übersehen habe.

Antwort

49

Es gibt zwei getrennte Fragen hier:

  1. Warum Shapeless Nutzungsart Mitglieder anstelle von Typparametern in einigen Fällen in einigen Typklassen?
  2. Warum enthält Shapeless Aux Aliase in den Begleitobjekten dieser Typklassen?

Ich werde mit der zweiten Frage beginnen, weil die Antwort einfacher ist: Die Aliasnamen Aux sind völlig syntaktisch. Sie haben nie haben, um sie zu verwenden. Angenommen, wir eine Methode schreiben möchten, wird nur, wenn sie mit zwei hlists, die die gleiche Länge haben genannt kompilieren:

import shapeless._, ops.hlist.Length 

def sameLength[A <: HList, B <: HList, N <: Nat](a: A, b: B)(implicit 
    al: Length.Aux[A, N], 
    bl: Length.Aux[B, N] 
) =() 

Die Length Typklasse hat einen Typ-Parameter (für den HList-Typ) und ein Typ Mitglied (für die Nat). Die Length.Aux Syntax macht es relativ einfach, an die Nat Typ-Member in der impliziten Parameterliste zu beziehen, aber es ist nur eine Bequemlichkeit-Folgendes ist genau äquivalent:

def sameLength[A <: HList, B <: HList, N <: Nat](a: A, b: B)(implicit 
    al: Length[A] { type Out = N }, 
    bl: Length[B] { type Out = N } 
) =() 

Die Aux Version hat ein paar Vorteile gegenüber out schreiben die Typ-Verfeinerungen auf diese Weise: Es ist weniger laut und es erfordert nicht, dass wir uns an den Namen des Typs member erinnern. Das sind rein ergonomische Probleme - die Aliasnamen machen unseren Code ein wenig einfacher zu lesen und zu schreiben, aber sie ändern nicht, was wir mit dem Code in irgendeiner sinnvollen Weise tun können oder was nicht.

Die Antwort auf die erste Frage ist ein wenig komplexer. In vielen Fällen, einschließlich meiner sameLength, gibt es keinen Vorteil für Out ist ein Typ Mitglied anstelle eines Typs Parameter. Da Scala doesn't allow multiple implicit parameter sections ist, benötigen wir N als einen Typparameter für unsere Methode, wenn wir überprüfen möchten, ob die beiden Length Instanzen den gleichen Out Typ haben. An diesem Punkt könnte auch die Out on Length ein Typparameter sein (zumindest aus unserer Sicht als die Autoren von sameLength). In anderen Fällen können wir jedoch die Tatsache ausnutzen, dass Shapeless manchmal (ich spreche speziell über wo in einem Moment) Typmember anstelle von Typparametern verwendet. Angenommen, wir eine Methode schreiben wollen, die eine Funktion zurück, die einen bestimmten Fall Klassentyp in einen HList umwandeln:

def converter[A](implicit gen: Generic[A]): A => gen.Repr = a => gen.to(a) 

Jetzt können wir es wie folgt verwendet werden:

case class Foo(i: Int, s: String) 

val fooToHList = converter[Foo] 

Und Wir werden eine nette Foo => Int :: String :: HNil bekommen. Wenn Generic ‚s Repr wurden stattdessen ein Typparameter eines Typs Mitglied, würden wir so etwas schreiben müssen statt:

// Doesn't compile 
def converter[A, R](implicit gen: Generic[A, R]): A => R = a => gen.to(a) 

Scala nicht teilweise Anwendung der Typparameter nicht unterstützt, so dass jedes Mal, wenn wir dies nennen (hypothetischen) Methode würden wir beide Typen Parameter angeben müssen, da wir A angeben möchten:

val fooToHList = converter[Foo, Int :: String :: HNil] 

Dies macht es im Grunde wertlos, da der ganze Punkt die allgemeinen Maschinen Figur aus der Darstellung zu lassen war.

Wenn ein Typ eindeutig durch die anderen Parameter einer Typklasse bestimmt wird, wird Shapeless im Allgemeinen ein Typmember anstelle eines Typparameters. Jede Fallklasse hat eine einzige generische Darstellung, so dass Generic einen Typparameter (für den Fallklassentyp) und einen Typmember (für den Darstellungstyp) hat; jeder HList hat eine einzige Länge, so Length hat einen Typ-Parameter und ein Typ Mitglied usw.

machen eindeutig bestimmten Typen Mitglieder anstelle von Typparametern geben bedeutet, dass, wenn wir wollen, dass sie verwenden nur als pfadabhängige Typen (wie in der ersten converter oben), wir können, aber wenn wir sie verwenden möchten, als ob sie Typparameter waren, können wir immer entweder die Typverfeinerung (oder die syntaktisch bessere Aux Version) ausschreiben. Wenn Shapeless diese Typen von Anfang an eingibt, ist es nicht möglich, in die entgegengesetzte Richtung zu gehen.

Als Randbemerkung, diese Beziehung zwischen der Typ einer Typklasse „Parameter“ (Ich bin mit Anführungszeichen, da sie nicht Parameter im wörtlichen Scala Sinn sein kann) ist ein "functional dependency" in Sprachen wie Haskell genannt, aber Sie sollten nicht das Gefühl haben, dass Sie etwas über funktionale Abhängigkeiten in Haskell verstehen müssen, um zu verstehen, was in Shapeless passiert.