Co to są granice kontekstu i widoku Scali?

267

W prosty sposób, czym są granice kontekstu i widoku i jaka jest między nimi różnica?

Świetne byłyby też niektóre łatwe do naśladowania przykłady!

chrsan
źródło

Odpowiedzi:

477

Myślałem, że zostało to już zadane, ale jeśli tak, pytanie nie jest widoczne w „pokrewnym” pasku. Oto on:

Co to jest granica widoku?

Ograniczenie widoku było mechanizmem wprowadzonym w Scali, aby umożliwić użycie jakiegoś typu A tak, jakby był jakimś typem B. Typowa składnia jest następująca:

def f[A <% B](a: A) = a.bMethod

Innymi słowy, Apowinna mieć niejawną konwersję na Bdostępną, aby można było wywoływać Bmetody na obiekcie typu A. Najczęstszym zastosowaniem obwiedni widoku w bibliotece standardowej (w każdym razie przed Scalą 2.8.0) jest Ordered:

def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b

Ponieważ można przekształcić Aw Ordered[A]i ponieważ Ordered[A]definiuje metodę <(other: A): Boolean, mogę użyć wyrażenia a < b.

Należy pamiętać, że granice widoków są przestarzałe , należy ich unikać.

Co to jest granica kontekstu?

Granice kontekstu zostały wprowadzone w Scali 2.8.0 i są zwykle używane z tak zwanym wzorcem klasy typu , wzorem kodu, który emuluje funkcjonalność zapewnianą przez klasy typu Haskell, choć w bardziej szczegółowy sposób.

Podczas gdy powiązania widoku można używać z typami prostymi (na przykład A <% String), powiązanie kontekstu wymaga sparametryzowanego typu , takiego jak Ordered[A]powyżej, ale w przeciwieństwie do niego String.

Powiązanie kontekstu opisuje wartość domyślną , zamiast niejawnej konwersji granicy widoku . Służy do deklarowania, że ​​dla niektórych typów Adostępna jest domyślna wartość typu B[A]. Składnia wygląda następująco:

def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]

Jest to bardziej mylące niż widok związany, ponieważ nie jest od razu jasne, jak go używać. Typowym przykładem użycia w Scali jest:

def f[A : ClassManifest](n: Int) = new Array[A](n)

ArrayInicjalizacji na typ sparametryzowane wymaga ClassManifestmają być dostępne dla tajemniczych powodów związanych z usunięciem typu i charakteru wymazywania tablic.

Kolejny bardzo popularny przykład w bibliotece jest nieco bardziej złożony:

def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)

Tutaj implicitlysłuży do pobrania pożądanej wartości domyślnej, jednej z typów Ordering[A], która to klasa definiuje metodę compare(a: A, b: A): Int.

Zobaczymy inny sposób na zrobienie tego poniżej.

Jak wdrażane są granice widoku i granice kontekstu?

Nie powinno dziwić, że zarówno granice widoku, jak i granice kontekstu są implementowane z niejawnymi parametrami, biorąc pod uwagę ich definicję. W rzeczywistości pokazana przeze mnie składnia to cukry syntaktyczne do tego, co naprawdę się dzieje. Zobacz poniżej, jak usuwają cukier:

def f[A <% B](a: A) = a.bMethod
def f[A](a: A)(implicit ev: A => B) = a.bMethod

def g[A : B](a: A) = h(a)
def g[A](a: A)(implicit ev: B[A]) = h(a)

Można oczywiście napisać je w pełnej składni, co jest szczególnie przydatne w przypadku granic kontekstu:

def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)

Do czego wykorzystywane są Granice widoku?

Granice widoku są używane głównie w celu wykorzystania wzorca alfonsa mojej biblioteki , za pomocą którego jedna „dodaje” metody do istniejącej klasy, w sytuacjach, w których chcesz w jakiś sposób zwrócić oryginalny typ. Jeśli nie musisz w żaden sposób zwracać tego typu, nie potrzebujesz powiązania widoku.

Klasycznym przykładem użycia widoku ograniczonego jest obsługa Ordered. Pamiętaj, że Intnie jest to Orderedna przykład konwersja niejawna. Podany wcześniej przykład wymaga powiązania widoku, ponieważ zwraca typ nie przekonwertowany:

def f[A <% Ordered[A]](a: A, b: A): A = if (a < b) a else b

Ten przykład nie będzie działać bez granic widoku. Gdybym jednak miał zwrócić inny typ, nie potrzebuję już powiązanego widoku:

def f[A](a: Ordered[A], b: A): Boolean = a < b

Konwersja tutaj (w razie potrzeby) następuje przed przekazaniem parametru do f, więc fnie trzeba o tym wiedzieć.

Poza Orderedtym najczęstszym zastosowaniem z biblioteki jest obsługa Stringi Array, które są klasami Java, tak jakby były kolekcjami Scala. Na przykład:

def f[CC <% Traversable[_]](a: CC, b: CC): CC = if (a.size < b.size) a else b

Gdyby ktoś spróbował to zrobić bez granic widoku, typem zwrotu a Stringbyłby WrappedString(Scala 2.8) i podobnie dla Array.

To samo dzieje się, nawet jeśli typ jest używany tylko jako parametr typu typu zwracanego:

def f[A <% Ordered[A]](xs: A*): Seq[A] = xs.toSeq.sorted

Do czego służą Granice kontekstu?

Granice kontekstu są używane głównie w tak zwanym wzorcu klasowym , jako odniesienie do klas typów Haskella. Zasadniczo ten wzorzec implementuje alternatywę dla dziedziczenia, udostępniając funkcjonalność poprzez rodzaj niejawnego wzorca adaptera.

Klasycznym przykładem jest Scala 2.8 Ordering, która zastąpiła Orderedcałą bibliotekę Scali. Wykorzystanie jest:

def f[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a else b

Chociaż zwykle widzisz to tak napisane:

def f[A](a: A, b: A)(implicit ord: Ordering[A]) = {
    import ord.mkOrderingOps
    if (a < b) a else b
}

Które wykorzystują niektóre niejawne konwersje wewnątrz, Orderingktóre umożliwiają tradycyjny styl operatora. Innym przykładem w Scali 2.8 jest Numeric:

def f[A : Numeric](a: A, b: A) = implicitly[Numeric[A]].plus(a, b)

Bardziej złożonym przykładem jest użycie nowej kolekcji CanBuildFrom, ale odpowiedź na to pytanie jest już bardzo długa, więc uniknę tego tutaj. Jak wspomniano wcześniej, istnieje ClassManifestużycie, które jest wymagane do inicjowania nowych tablic bez konkretnych typów.

Kontekst związany z wzorcem typu czcionki jest znacznie bardziej prawdopodobne, że będzie używany przez twoje własne klasy, ponieważ umożliwiają rozdzielenie problemów, podczas gdy granice widoku można uniknąć w swoim własnym kodzie dzięki dobrym projektom (jest on używany głównie do obejścia projektu innej osoby ).

Chociaż było to możliwe przez długi czas, użycie granic kontekstu naprawdę się rozwinęło w 2010 roku i obecnie znajduje się w pewnym stopniu w większości najważniejszych bibliotek i frameworków Scali. Najbardziej skrajnym przykładem jego użycia jest biblioteka Scalaz, która wnosi do Scali wiele mocy Haskell. Polecam lekturę wzorców typów, aby lepiej poznać wszystkie sposoby jej wykorzystania.

EDYTOWAĆ

Powiązane interesujące pytania:

Daniel C. Sobral
źródło
9
Dziękuję bardzo. Wiem, że już na to odpowiedziano, i może wtedy nie czytałem wystarczająco uważnie, ale twoje wyjaśnienie tutaj jest najbardziej jasne, jakie widziałem. Dziękuję jeszcze raz.
chrsan
3
@chrsan Dodałem jeszcze dwie sekcje, szczegółowo omawiając, gdzie każda z nich korzysta.
Daniel C. Sobral,
2
Myślę, że to doskonałe wytłumaczenie. Chciałbym przetłumaczyć to na mojego niemieckiego bloga (dgronau.wordpress.com), jeśli nie mam nic przeciwko.
Landei
3
To zdecydowanie najlepsze i najbardziej wyczerpujące wyjaśnienie tego tematu, jakie do tej pory znalazłem. Na prawdę bardzo ci dziękuję!
fotNelton,
2
Sooo, kiedy wychodzi Twoja książka Scala i gdzie mogę ją kupić :)
wfbarksdale