2014-12-22 7 views
8

Patrząc na metodę <> w następującej scala klasie gładkiej, od http://slick.typesafe.com/doc/2.1.0/api/index.html#scala.slick.lifted.ToShapedValue, to przypomina mi that iconic stackoverflow thread about scala prototypes.Odszyfrowanie jednego z najcięższych prototypów metody scala (gładkiego)

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

Może ktoś odważny i wiedzę zapewniają instruktażu artykułować tej długiej definicji prototypu, dokładnie wyjaśniając wszystko w swoim rodzaju kowariancji/niezmienności, dwu list parametrów i innych zaawansowanych aspektów Scala?

Ćwiczenie to znacznie ułatwi obsługę podobnie skonstruowanych prototypów!

+3

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

Odpowiedz

20

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?

+0

Mogę powiedzieć tylko jedno. Łał. – matanster

+0

To, BTW, jest jedną z najlepszych rzeczy na temat potężnego, ekspresywnego, mocnego, statycznego typu systemu: można powiedzieć, po prostu patrząc na typy, co robi metoda, nie trzeba patrzeć na dokumentację, nie mówiąc już o kodzie źródłowym , do diabła, nie potrzebujesz nawet imienia *! –

+0

tak, wydaje mi się to dokładne :) Btw zastanawiał się, dlaczego obecny był typ zwrotu "Opcja". Teraz zastanawiasz się, co jest lepsze w przypadku Brak –

Powiązane problemy