Istnieją dwa odrębne pytania tutaj:
- Dlaczego członkowie typu Shapeless użyty zamiast parametrów typu, w niektórych przypadkach, w niektórych klasach typu?
- Dlaczego Shapeless zawiera aliasy typu
Aux
w obiektach towarzyszących tych klas?
Zacznę od drugiego pytania, ponieważ odpowiedź jest prostsza: aliasy typu Aux
są całkowicie syntaktyczną wygodą. Nie musisz nigdy używać . Na przykład, załóżmy, że chcemy napisać metodę, która będzie kompilować, gdy wywołana tylko z dwoma hlists które mają taką samą długość:
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]
) =()
Klasa Length
typ ma jeden parametr typu (dla typu HList
) oraz jeden element typu (dla Nat
). Składnia Length.Aux
sprawia, że stosunkowo łatwo odnieść się do członka Nat
typu w niejawnej liście parametrów, ale jest to tylko wygoda, po to dokładnie równoważne:
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 }
) =()
Wersja Aux
ma kilka zalet w stosunku wypisywanie udoskonalenie typu w ten sposób: jest mniej hałaśliwe i nie wymaga od nas zapamiętywania nazwy elementu typu. Są to jednak kwestie czysto ergonomiczne - aliasy Aux
powodują, że nasz kod jest trochę łatwiejszy w czytaniu i pisaniu, ale nie zmieniają tego, co możemy lub nie możemy zrobić z kodem w jakikolwiek znaczący sposób.
Odpowiedź na pierwsze pytanie jest nieco bardziej złożona. W wielu przypadkach, w tym w moim sameLength
, nie ma żadnej korzyści z tego, że Out
jest elementem typu, a nie parametrem typu. Ponieważ Scala doesn't allow multiple implicit parameter sections, potrzebujemy N
, aby być parametrem typu dla naszej metody, jeśli chcemy sprawdzić, czy dwie instancje Length
mają ten sam typ Out
. W tym momencie Out
na Length
może równie dobrze być parametrem typu (przynajmniej z naszej perspektywy jako autorzy sameLength
).
W innych przypadkach możemy skorzystać z faktu, że czasami Shapeless (będę mówić o konkretnie gdzie za chwilę) używa elementów typu zamiast parametrów typu. Na przykład, załóżmy, że chcemy napisać metodę, która będzie zwracać funkcję, która zamieni określony typ klasy sprawę do internetowej HList
:
def converter[A](implicit gen: Generic[A]): A => gen.Repr = a => gen.to(a)
Teraz możemy używać go tak:
case class Foo(i: Int, s: String)
val fooToHList = converter[Foo]
I dostaniemy miłe Foo => Int :: String :: HNil
. Jeśli Generic
„s Repr
zostały parametrem typu zamiast członka typu, musielibyśmy napisać coś takiego zamiast:
// Doesn't compile
def converter[A, R](implicit gen: Generic[A, R]): A => R = a => gen.to(a)
Scala nie obsługuje częściowego stosowania parametrów typu, więc za każdym razem nazywamy to metoda (hipotetyczny) musielibyśmy określić oba parametry typu, ponieważ chcemy, aby określić A
:
val fooToHList = converter[Foo, Int :: String :: HNil]
to sprawia, że w zasadzie bezwartościowe, ponieważ cały sens był pozwolić rodzajowe maszynowy zorientować się w reprezentacji.
Ogólnie, gdy typ jest jednoznacznie określony przez inne parametry klasy typu, Shapeless spowoduje, że będzie on elementem typu, a nie parametrem typu. Każda klasa sprawy ma jedną reprezentację ogólną, więc Generic
ma jeden parametr typu (dla typu klasy sprawy) i jeden element typu (dla typu reprezentacji); każdy kod HList
ma jedną długość, więc ma jeden parametr i jeden element itd.
Tworzenie unikatowych typów elementów typu zamiast parametrów typu oznacza, że jeśli chcemy używać ich tylko jako typów zależnych od ścieżki (tak jak w pierwszym converter
powyżej), możemy, ale jeśli chcemy ich użyć tak, jakby były parametrami typu, zawsze możemy albo napisać wyrafinowanie typu (albo ładniej syntaktyczną wersję Aux
). Jeśli Shapeless tworzył od początku te typy parametrów, nie byłoby możliwe przejście w przeciwnym kierunku.
Na marginesie, to zależność między typem „parametry” klasy Type'S (używam cudzysłowu, ponieważ nie może być parametry w dosłownym Scala sensie) jest nazywany "functional dependency" w językach takich jak Haskell, ale nie powinieneś czuć, że musisz zrozumieć cokolwiek na temat funkcjonalnych zależności w Haskell, aby uzyskać to, co dzieje się w Shapeless.