2015-11-15 10 views
23

Sto cercando di capire come funziona Generic (e TypeClass). Il wiki di github è molto scarso su esempi e documentazione. C'è una pagina di post di blog/documentazione canonica che descrive Generic e TypeClass in dettaglio?Shapeless: Generic.Aux

In concreto, qual è la differenza tra questi due metodi ?:

def find1[T](implicit gen: Generic[T]): Generic[T] = gen 
def find2[T](implicit gen: Generic[T]): Generic[T] { type Repr = gen.Repr } = gen 

dato

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] 
} 

risposta

47

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.

+1

Perché no '' 'def find2 [T] (gen implicito: generico [T]): Generic.Aux [T, gen.Repr] = gen'''? – crak

+6

@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. –

+13

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

Problemi correlati