Es gibt zwei getrennte Fragen hier:
- Warum Shapeless Nutzungsart Mitglieder anstelle von Typparametern in einigen Fällen in einigen Typklassen?
- 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.