problemi coinvolti nel modo in cui Generic
e TypeClass
sono implementate e che cosa fare sono abbastanza differenti che probabilmente meritano domande separate, quindi mi limiterò a Generic
qui.
Generic
fornisce un mapping da classi di casi (e tipi potenzialmente simili) a elenchi eterogenei. Qualsiasi classe case ha una rappresentazione hlist univoca, ma ogni hlist fornita corrisponde ad un numero molto, molto grande di potenziali case classes. Ad esempio, se abbiamo le seguenti classi case:
case class Foo(i: Int, s: String)
case class Bar(x: Int, y: String)
La rappresentazione HList fornito da Generic
sia Foo
e Bar
è Int :: String :: HNil
, che è anche la rappresentazione per (Int, String)
e le altre classi case potremmo definire con questi due tipi in questo ordine.
(Come nota a margine, LabelledGeneric
ci permette di distinguere tra Foo
e Bar
, poiché include i nomi dei membri nella rappresentazione come stringhe tipo di livello.)
In genere vogliamo essere in grado di specificare il caso classificare e lasciare che Shapeless comprenda la rappresentazione generica (unica) e rendendo Repr
un membro di tipo (invece di un parametro di tipo) ci consente di farlo in modo abbastanza pulito. Se il tipo di rappresentazione hlist era un parametro di tipo, allora i tuoi metodi find
dovevano avere anche un parametro di tipo Repr
, il che significa che non saresti in grado di specificare solo lo T
e di avere il Repr
dedotto.
Fare un Repr
un membro del tipo ha senso solo perché lo Repr
è determinato in modo univoco dal primo parametro di tipo. Immagina una classe di tipo come Iso[A, B]
che attesta che i documenti A
e B
sono isomorfi. Questa classe tipo è molto simile a Generic
, ma non lo fa in modo univoco A
Dermine B
-non si può semplicemente chiedere "qual è il tipo che è isomorfo a A
?" - in modo che non sarebbe utile per fare un tipo di B
membro (anche se potremmo se volessimo davvero- Iso[A]
non significherebbe davvero nulla).
Il problema con i membri del tipo è che sono facili da dimenticare, e una volta che se ne sono andati, sono spariti per sempre. Il fatto che il tipo di reso del tuo find1
non sia raffinato (ovvero non include il membro del tipo) significa che l'istanza Generic
restituita è praticamente inutile.Per esempio, la tipo statico di res0
qui potrebbe anche essere 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
^
Quando Generic.materialize
macro di informe crea l'istanza Generic[Foo]
stiamo chiedendo, è staticamente tipizzato come Generic[Foo] { type Repr = Int :: String :: HNil }
, in modo che il gen
argomento che il compilatore mani a find1
ha tutte le informazioni statiche di cui abbiamo bisogno. Il problema è che abbiamo quindi esplicitamente richiamato quel tipo su un semplice vecchio non raffinato Generic[Foo]
, e da quel punto sul compilatore non si sa cosa sia lo Repr
per quell'istanza.
I tipi dipendenti dalla scala di Scala ci consentono di non dimenticare il perfezionamento senza aggiungendo un altro parametro di tipo al nostro metodo. Nella tua find2
, il compilatore conosce in modo statico il Repr
per l'incoming gen
, in modo che quando si dice che il tipo di ritorno è Generic[T] { type Repr = gen.Repr }
, sarà in grado di tenere traccia di tali informazioni:
scala> find2[Foo].to(Foo(1, "ABC"))
res2: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 1 :: ABC :: HNil
scala> res2.head
res3: Int = 1
Riassumendo: Generic
ha un parametro di tipo T
che determina in modo univoco il suo membro di tipo Repr
, Repr
è un membro di tipo anziché un parametro di tipo, quindi non è necessario includerlo in tutti i tipi di firma e tipi dipendenti dal percorso lo rendono possibile, consentendoci per tenere traccia di Repr
anche se non è nelle nostre firme di tipo.
Perché no '' 'def find2 [T] (gen implicito: generico [T]): Generic.Aux [T, gen.Repr] = gen'''? – crak
@crak È possibile scrivere anche questo: è esattamente equivalente all'utilizzo della raffinatezza del tipo. Penso che spesso le persone si blocchino su Aux, anche se in realtà è solo una comodità sintattica, quindi l'ho esclusa da questa risposta. –
Travis, volevo solo dire che apprezzo molto leggere le tue risposte e i post del blog su informe. Roba non banale molto ben spiegato. Continuate così! – Haspemulator