Bezkształtny: Rodzajowy.Aux
Próbuję zrozumieć, jak działa Generic
(i TypeClass
też). Wiki na github jest bardzo uboga w przykłady i dokumentację. Czy istnieje kanoniczny wpis na blogu / strona dokumentacji opisująca Generic
i TypeClass
szczegółowo?
W betonie, jaka jest różnica między tymi dwoma metodami?:
def find1[T](implicit gen: Generic[T]): Generic[T] = gen
def find2[T](implicit gen: Generic[T]): Generic[T] { type Repr = gen.Repr } = gen
Podane
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]
}
1 answers
Kwestie związane z tym, jak Generic
i TypeClass
są realizowane i co robią, są na tyle różne, że prawdopodobnie zasługują na osobne pytania, więc zostanę przy Generic
tutaj.
Generic
zapewnia mapowanie z klas przypadków (i potencjalnie podobnych typów) do list heterogenicznych. Każda klasa case ma unikalną reprezentację hlist, ale każda dana klasa hlist odpowiada bardzo, bardzo dużej liczbie potencjalnych klas case. Na przykład, jeśli mamy następujący przypadek klasy:
case class Foo(i: Int, s: String)
case class Bar(x: Int, y: String)
Reprezentacja hlist dostarczona przez Generic
dla Foo
i Bar
jest Int :: String :: HNil
, która jest również reprezentacją dla (Int, String)
i innych klas przypadków, które możemy zdefiniować za pomocą tych dwóch typów w tej kolejności.
(na marginesie, LabelledGeneric
pozwala nam odróżnić Foo
i Bar
, ponieważ zawiera nazwy członków w reprezentacji jako ciągi znaków na poziomie typu.)
Ogólnie chcemy być w stanie określić klasę case I niech (unikalna) reprezentacja generyczna i uczynienie Repr
członkiem typu (zamiast parametru typu) pozwala nam to zrobić całkiem czysto. Jeśli Typ reprezentacji hlist jest parametrem typu, to twoje metody find
muszą mieć również parametr typu Repr
, co oznacza, że nie będziesz mógł podać tylko T
i wywnioskować Repr
.
Tworzenie Repr
członka typu ma sens tylko dlatego, że {[15] } jest jednoznacznie określony przez pierwszy parametr typu. Wyobraź sobie klasę typu Iso[A, B]
, która świadczy o tym, że A
i B
są izomorficzne. Ta klasa typu jest bardzo podobna do Generic
, ale A
nie wyróżnia się B
-nie możemy po prostu zapytać "czym jest Typ, który jest izomorficzny do A
?"- więc nie byłoby przydatne, aby B
członek typu (chociaż moglibyśmy, gdybyśmy naprawdę chcieli - Iso[A]
tylko tak naprawdę nic nie znaczą).
Problem z członkami typu polega na tym, że łatwo o nich zapomnieć, a gdy już ich nie ma, są odszedł na zawsze. Fakt, że zwracany typ find1
nie jest dopracowany (tzn. nie zawiera elementu type) oznacza, że zwracana instancja Generic
jest praktycznie bezużyteczna. Na przykład statyczny typ res0
tutaj równie dobrze może być 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
^
Gdy makro Generic.materialize
shapeless tworzy instancję Generic[Foo]
, o którą prosimy, jest ona wpisywana statycznie jako Generic[Foo] { type Repr = Int :: String :: HNil }
, więc argument gen
, który kompilator przekazuje find1
zawiera wszystkie potrzebne nam informacje statyczne. Problem w tym, że my następnie jawnie up-cast ten typ do zwykłego starego nierafinowane Generic[Foo]
, i od tego momentu kompilator nie wie, co Repr
jest dla tej instancji.
Typy Scali zależne od ścieżki dają nam sposób, aby nie zapomnieć o udoskonaleniu bez dodawania innego parametru typu do naszej metody. W twoim find2
kompilator statycznie zna Repr
dla przychodzących gen
, więc kiedy powiesz, że typem zwracanym jest Generic[T] { type Repr = gen.Repr }
, będzie w stanie śledzić to informacje:
scala> find2[Foo].to(Foo(1, "ABC"))
res2: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 1 :: ABC :: HNil
scala> res2.head
res3: Int = 1
Podsumowując: Generic
posiada parametr type T
, który jednoznacznie określa jego typ Repr
, Repr
jest członkiem type zamiast parametru type, dzięki czemu nie musimy umieszczać go we wszystkich naszych podpisach type, A typy zależne od ścieżki umożliwiają to, pozwalając nam śledzić Repr
, nawet jeśli nie ma go w naszych podpisach type.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-16 15:27:07