Odpowiedzi:
Myślałem, że zostało to już zadane, ale jeśli tak, pytanie nie jest widoczne w „pokrewnym” pasku. Oto on:
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, A
powinna mieć niejawną konwersję na B
dostępną, aby można było wywoływać B
metody 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ć A
w 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ć.
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 A
dostę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)
Array
Inicjalizacji na typ sparametryzowane wymaga ClassManifest
mają 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 implicitly
sł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.
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)
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 Int
nie jest to Ordered
na 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 f
nie trzeba o tym wiedzieć.
Poza Ordered
tym najczęstszym zastosowaniem z biblioteki jest obsługa String
i 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 String
był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
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 Ordered
całą 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, Ordering
któ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 ClassManifest
uż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: