Ok, rzućmy okiem:
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]
}
Klasa jest AnyVal
wrapper; podczas gdy ja nie widzę szybko konwersji, wygląda ona jak wzór "pimp my library". Zgaduję więc, że ma to na celu dodanie <>
jako "metody rozszerzenia" na niektóre (lub wszystkie) typy.
@inline
to adnotacja, sposób umieszczania metadanych na, cóż, cokolwiek; ten jest wskazówką dla kompilatora, że należy to zaznaczyć. <>
to nazwa metody - wiele rzeczy, które wyglądają jak "operatorzy" to zwyczajne metody w scala.
Łączona dokumentacja już rozszerzyła R: ClassTag
na zwykłą R
i implicit ClassTag[R]
- jest to "kontekst związany" i jest to po prostu cukier syntaktyczny. ClassTag
jest rzeczą generowaną przez kompilator, która istnieje dla każdego typu (konkretnego) i pomaga w odbiciu, więc jest to wskazówka, że metoda prawdopodobnie w pewnym momencie dokona pewnej refleksji na temat R
.
Teraz mięso: jest to metoda ogólna, sparametryzowana przez dwa typy: [R, U]
. Jego argumenty są dwie funkcje, f: U => R
i g: R => Option[U]
. To trochę przypomina funkcjonalną koncepcję- konwersja z U
na R
, która zawsze działa, i konwersja z R
na U
, która czasami nie działa.
Interesującą częścią podpisu (rodzaj) jest na końcu implicit shape
. Shape
jest opisany jako "typ", więc jest to prawdopodobnie najlepiej rozumiane jako "ograniczenie": ogranicza możliwe typy U
i R
, które możemy nazwać tą funkcją, tylko dla tych, dla których dostępny jest odpowiedni Shape
.
Patrząc na the documentation forShape
widzimy, że cztery typy są Level
, Mixed
, Unpacked
i Packed
. Więc ograniczenie to: musi istnieć Shape
, którego „poziom” jest jakiś podtyp FlatShapeLevel
, gdzie typ Mixed
jest T
i rodzaj Unpacked
jest R
(typ Packed
może być dowolnego typu).
Jest to funkcja poziomu wysokiego poziomu, która oznacza, że R
to "rozpakowana wersja" T
.Aby ponownie skorzystać z przykładu z dokumentacji Shape
, jeśli T
to (Column[Int], Column[(Int, String)], (Int, Option[Double]))
, to będzie (i działa tylko dla FlatShapeLevel
, ale zamierzam wykonać wywołanie oceny, które prawdopodobnie nie jest ważne). U
jest, co ciekawe, całkowicie nieskrępowany.
Dzięki temu możemy utworzyć MappedProjection[unpacked-version-of-T, U]
z dowolnego T
, zapewniając funkcje konwersji w obu kierunkach. Więc w prostej wersji może T
jest Column[String]
- reprezentacja kolumny String
w bazie danych - i chcemy ją reprezentować jako typ aplikacji, np. EmailAddress
. Tak więc R=String
, U=EmailAddress
, a my zapewniamy funkcje konwersji w obu kierunkach: f: EmailAddress => String
i g: String => Option[EmailAddress]
. Ma to sens w następujący sposób: każdy element EmailAddress
może być reprezentowany jako String
(przynajmniej lepiej by było, gdybyśmy chcieli móc je przechowywać w bazie danych), ale nie każdy String
jest prawidłowym EmailAddress
. Jeśli nasza baza danych miała np. "http://www.foo.com/" w kolumnie adresu e-mail nasz g
zwróciłby None
, a Slick poradziłby sobie z tym z wdziękiem.
MappedProjection
sama jest, niestety, nieudokumentowana. Ale domyślam się, że to leniwy obraz rzeczy, o którą możemy zapytać; gdzie mieliśmy Column[String]
, teraz mamy rzecz pseudokolumnową, której typ (bazowy) to EmailAddress
. Dzięki temu możemy napisać pseudo-zapytania, takie jak "wybierz od użytkowników, gdzie emailAddress.domain =" gmail.com "", co byłoby niemożliwe do zrobienia bezpośrednio w bazie danych (która nie wie, która część adresu e-mail jest domena), ale można to łatwo zrobić za pomocą kodu. Przynajmniej, to moje najgorsze przypuszczenie, co może zrobić.
Możliwe, że funkcja może być bardziej przejrzysta poprzez użycie standardowego typu Prism
(np. Z Monocle), zamiast bezpośredniego przekazywania pary funkcji. Używanie funkcji niejawnej do zapewnienia funkcji poziomu poziomu jest niewygodne, ale konieczne; w całkowicie zależnym języku (np. Idris), możemy napisać funkcję naszego poziomu jako funkcję (coś podobnego do def unpackedType(t: Type): Type = ...
). Tak koncepcyjnie, funkcja ta wygląda mniej więcej tak:
def <>[U](p: Prism[U, unpackedType(T)]): MappedProjection[unpackedType(T), U]
Mam nadzieję, że to wyjaśnia niektóre z procesu myślowego czytania nową, nieznaną funkcję. W ogóle nie znam Slicka, więc nie mam pojęcia, jak bardzo jestem dokładny, do czego służy ten <>
- czy dobrze to rozumiem?
Zaczynasz. Jest kilka osób, którym szczególnie zależy, ale nie wiele. Więc spróbuj wytłumaczyć, a gdzie utkniesz, pewnie inni w to wkroczą. –