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