2014-12-22 13 views
8

Guardando il metodo <> nella seguente classe scala slick, da http://slick.typesafe.com/doc/2.1.0/api/index.html#scala.slick.lifted.ToShapedValue, mi viene in mente lo that iconic stackoverflow thread about scala prototypes.Decifrare uno dei più duri prototipi del metodo scala (slick)

def <>[R, U](f: (U) ⇒ R, g: (R) ⇒ Option[U]) 
(implicit arg0: ClassTag[R], shape: Shape[_ <: FlatShapeLevel, T, U, _]): 
MappedProjection[R, U] 

Qualcuno può audace e competente fornire un'articolata procedura dettagliata di quella lunga definizione del prototipo, chiarendo con attenzione tutto il suo tipo di covarianza/invarianza, elenchi di parametri doppie, e altri aspetti avanzati Scala?

Questo esercizio aiuterà anche a gestire prototipi similmente contorti!

+3

Si inizia. Ci sono alcune persone a cui importa in particolare, ma non molte. Quindi prova una spiegazione, e dove rimani bloccato, probabilmente altri entreranno. –

risposta

20

Ok, diamo un'occhiata:

class ToShapedValue[T](val value: T) extends AnyVal { 
    ... 
    @inline def <>[R: ClassTag, U](f: (U) ⇒ R, g: (R) ⇒ Option[U])(implicit shape: Shape[_ <: FlatShapeLevel, T, U, _]): MappedProjection[R, U] 
} 

La classe è un AnyVal involucro; anche se non riesco a vedere la conversione dello implicit da un rapido sguardo, ha l'odore del modello "pimp my library". Quindi immagino che questo abbia lo scopo di aggiungere <> come "metodo di estensione" su alcuni (o forse tutti) tipi.

@inline è un'annotazione, un modo per inserire i metadati su, beh, qualsiasi cosa; questo è un suggerimento per il compilatore che questo dovrebbe essere sottolineato. <> è il nome del metodo: un sacco di cose che sembrano "operatori" sono semplici metodi in scala.

La documentazione collegata ha già espanso lo R: ClassTag in ordinario R e un implicit ClassTag[R] - questo è un "contesto limitato" ed è semplicemente zucchero sintattico. ClassTag è una cosa generata dal compilatore che esiste per ogni tipo (concreto) e aiuta a riflettere, quindi questo è un suggerimento che il metodo probabilmente farà qualche riflessione su un R ad un certo punto.

Ora, la carne: questo è un metodo generico, parametrizzato da due tipi: [R, U]. I suoi argomenti sono due funzioni, f: U => R e g: R => Option[U]. Sembra un po 'come il concetto funzionale Prism - una conversione da U a R che funziona sempre e una conversione da R a U che a volte non funziona.

La parte interessante della firma (sorta di) è il implicit shape alla fine. Shape è descritto come un "typeclass", quindi questo è probabilmente meglio pensato come un "vincolo": limita i possibili tipi U e R con cui possiamo chiamare questa funzione, solo quelli per i quali è disponibile uno Shape appropriato.

Guardando the documentation forShape, vediamo che i quattro tipi sono Level, Mixed, Unpacked e Packed. Così il vincolo è: ci deve essere un Shape, il cui "livello" è un po 'sottotipo di FlatShapeLevel, dove il tipo Mixed è T e il tipo Unpacked è R (il tipo Packed può essere di qualsiasi tipo).

Quindi, questa è una funzione a livello di carattere che esprime che R è "la versione scompattata di" T.Per utilizzare nuovamente l'esempio della documentazione Shape, se T è (Column[Int], Column[(Int, String)], (Int, Option[Double])) allora R sarà (Int, (Int, String), (Int, Option[Double]) (e funziona solo per FlatShapeLevel, ma farò una chiamata di giudizio che probabilmente non è importante). U è, abbastanza interessante, completamente non vincolato.

Quindi questo ci permette di creare un MappedProjection[unpacked-version-of-T, U] da qualsiasi T, fornendo funzioni di conversione in entrambe le direzioni. Quindi, in una versione semplice, forse T è un Column[String] - una rappresentazione di una colonna String in un database - e vogliamo rappresentarlo come un tipo specifico dell'applicazione, ad es. EmailAddress. Quindi, R=String, U=EmailAddress, e forniamo funzioni di conversione in entrambe le direzioni: f: EmailAddress => String e g: String => Option[EmailAddress]. È logico che sia così: ogni EmailAddress può essere rappresentato come String (almeno, sarebbe meglio se fosse necessario archiviarli nel database), ma non tutti gli String sono validi come EmailAddress. Se il nostro database avesse in qualche modo, ad es. "http://www.foo.com/" nella colonna dell'indirizzo email, il nostro g restituirebbe None e Slick potrebbe gestirlo con garbo.

MappedProjection è, purtroppo, non documentato. Ma immagino che sia una specie di rappresentazione pigra di una cosa che possiamo interrogare; dove avevamo uno Column[String], ora abbiamo una cosa pseudo-colonna il cui tipo (sottostante) è EmailAddress. Quindi questo potrebbe permetterci di scrivere pseudoquest come 'selezionare dagli utenti dove emailAddress.domain = "gmail.com"', che sarebbe impossibile fare direttamente nel database (che non sa quale parte di un indirizzo email è il dominio), ma è facile da fare con l'aiuto del codice. Almeno, questa è la mia ipotesi migliore su ciò che potrebbe fare.

Probabilmente la funzione potrebbe essere resa più chiara utilizzando un tipo standard Prism (ad esempio quello di Monocle) anziché passare esplicitamente una coppia di funzioni. Usare l'implicito per fornire una funzione di livello di tipo è scomodo ma necessario; in un linguaggio tipizzato in modo completamente dipendente (ad esempio Idris), potremmo scrivere la funzione di tipo livello come funzione (qualcosa come def unpackedType(t: Type): Type = ...). Quindi concettualmente, questa funzione sembra qualcosa di simile:

def <>[U](p: Prism[U, unpackedType(T)]): MappedProjection[unpackedType(T), U] 

Speriamo che questo spiega alcune del processo di pensiero di lettura di una nuova funzione sconosciuta. Non conosco affatto Slick, quindi non ho idea di quanto sono preciso riguardo a ciò per cui è usato questo <> - ho capito bene?

+0

Posso dire solo una cosa. Wow. – matanster

+0

Questo, BTW, è una delle grandi cose di un sistema di tipi potente, espressivo, forte, statico: puoi dire semplicemente guardando i tipi di metodo, non c'è bisogno di guardare la documentazione, per non parlare del codice sorgente , diamine, non hai nemmeno bisogno del * nome *! –

+0

sì, mi sembra accurato :) Btw si chiedeva perché fosse presente il tipo di ritorno "Opzione". Ora mi chiedo cosa faccia schifo in caso di None –