Jedną z nowych funkcji Scali 2.8 są ograniczenia kontekstowe. Co to jest kontekst i gdzie jest przydatne?
Oczywiście najpierw szukałem (i znalazłem na przykład to ), ale nie mogłem znaleźć żadnych naprawdę jasnych i szczegółowych informacji.
Jedną z nowych funkcji Scali 2.8 są ograniczenia kontekstowe. Co to jest kontekst i gdzie jest przydatne?
Oczywiście najpierw szukałem (i znalazłem na przykład to ), ale nie mogłem znaleźć żadnych naprawdę jasnych i szczegółowych informacji.
Odpowiedzi:
Znalazłeś ten artykuł ? Obejmuje nową funkcję związaną z kontekstem w kontekście ulepszeń tablicy.
Ogólnie parametr typu z powiązanym kontekstem ma postać [T: Bound]
; jest rozszerzany do zwykłego parametru typu T
wraz z niejawnym parametrem typu Bound[T]
.
Rozważ metodę, tabulate
która tworzy tablicę z wyników zastosowania danej funkcji f do zakresu liczb od 0 do określonej długości. Do wersji Scala 2.7 tabelę można zapisać w następujący sposób:
def tabulate[T](len: Int, f: Int => T) = {
val xs = new Array[T](len)
for (i <- 0 until len) xs(i) = f(i)
xs
}
W Scali 2.8 nie jest to już możliwe, ponieważ informacje o środowisku wykonawczym są niezbędne do stworzenia właściwej reprezentacji Array[T]
. Tę informację trzeba podać, przekazując ClassManifest[T]
do metody jako niejawny parametr:
def tabulate[T](len: Int, f: Int => T)(implicit m: ClassManifest[T]) = {
val xs = new Array[T](len)
for (i <- 0 until len) xs(i) = f(i)
xs
}
W skrócie, w parametrze typu można użyć ograniczenia kontekstuT
, dając:
def tabulate[T: ClassManifest](len: Int, f: Int => T) = {
val xs = new Array[T](len)
for (i <- 0 until len) xs(i) = f(i)
xs
}
Odpowiedź Roberta obejmuje techniczne szczegóły granic kontekstu. Podam ci moją interpretację ich znaczenia.
W Scali a View Bound ( A <% B
) przechwytuje pojęcie „może być postrzegane jako” (podczas gdy górna granica <:
obejmuje pojęcie „jest”). Kontekst powiązany ( A : C
) mówi „ma” o typie. Możesz przeczytać przykłady dotyczące manifestów jako „ T
ma Manifest
”. Przykład, do którego prowadzi link about Ordered
vs, Ordering
ilustruje różnicę. Metoda
def example[T <% Ordered[T]](param: T)
mówi, że parametr można postrzegać jako plik Ordered
. Porównać z
def example[T : Ordering](param: T)
co mówi, że parametr ma skojarzony Ordering
.
Pod względem użytkowania ustalenie konwencji zajęło trochę czasu, ale granice kontekstu są preferowane zamiast granic widoku ( granice widoku są teraz przestarzałe ). Jedną z sugestii jest to, że ograniczenie kontekstu jest preferowane, gdy trzeba przenieść niejawną definicję z jednego zakresu do innego bez konieczności bezpośredniego odwoływania się do niej (z pewnością dotyczy to ClassManifest
użycia do tworzenia tablicy).
Innym sposobem myślenia o granicach widoku i granicach kontekstu jest to, że pierwsza przenosi niejawne konwersje z zakresu wywołującego. Drugi przesyła niejawne obiekty z zakresu wywołującego.
has a
dla mnie więcej sensu]
(To jest uwaga w nawiasach. Najpierw przeczytaj i zrozum pozostałe odpowiedzi).
Granice kontekstu w rzeczywistości uogólniają granice widoku.
Tak więc, biorąc pod uwagę ten kod wyrażony za pomocą ograniczenia widoku:
scala> implicit def int2str(i: Int): String = i.toString
int2str: (i: Int)String
scala> def f1[T <% String](t: T) = 0
f1: [T](t: T)(implicit evidence$1: (T) => String)Int
Można to również wyrazić za pomocą Context Bound, za pomocą aliasu typu reprezentującego funkcje od typu F
do typu T
.
scala> trait To[T] { type From[F] = F => T }
defined trait To
scala> def f2[T : To[String]#From](t: T) = 0
f2: [T](t: T)(implicit evidence$1: (T) => java.lang.String)Int
scala> f2(1)
res1: Int = 0
Powiązany kontekst musi być używany z konstruktorem typu * => *
. Jednak konstruktor typu Function1
jest miły (*, *) => *
. Użycie aliasu typu częściowo powoduje zastosowanie drugiego parametru typu z typem String
, dając konstruktor typu poprawnego rodzaju do użycia jako powiązany z kontekstem.
Istnieje propozycja umożliwiająca bezpośrednie wyrażanie częściowo zastosowanych typów w Scali, bez użycia aliasu typu wewnątrz cechy. Możesz wtedy napisać:
def f3[T : [X](X => String)](t: T) = 0
From
typu To[String]
. Nie From
podajemy argumentu typu , więc odwołujemy się do konstruktora typu, a nie do typu. Ten konstruktor typu jest odpowiedniego rodzaju i może być używany jako powiązany z kontekstem - * -> *
. To wiąże parametr typu T
, wymagając niejawnego parametru typu To[String]#From[T]
. Rozwiń aliasy typów i voila, zostajesz z Function1[String, T]
.
To kolejna uwaga w nawiasach.
Jak zauważył Ben , powiązanie kontekstu reprezentuje ograniczenie typu „ma” między parametrem typu a klasą typu. Innymi słowy, reprezentuje ograniczenie, że istnieje niejawna wartość określonej klasy typu.
Korzystając z kontekstu, często trzeba ujawnić tę niejawną wartość. Na przykład, biorąc pod uwagę ograniczenie T : Ordering
, często będzie potrzebny egzemplarz, Ordering[T]
który spełnia to ograniczenie. Jak pokazano tutaj , możliwy jest dostęp do niejawnej wartości przy użyciu implicitly
metody lub nieco bardziej pomocnej context
metody:
def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) =
xs zip ys map { t => implicitly[Numeric[T]].times(t._1, t._2) }
lub
def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) =
xs zip ys map { t => context[T]().times(t._1, t._2) }