Co to jest „kontekst związany” w Scali?

115

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.

Jesper
źródło
8
sprawdź również to, aby zobaczyć wszystkie rodzaje granic: gist.github.com/257758/47f06f2f3ca47702b3a86c76a5479d096cb8c7ec
Arjan Blokzijl
2
Ta doskonała odpowiedź porównuje / kontrastuje granice kontekstu i granice widoku: stackoverflow.com/questions/4465948/…
Aaron Novstrup
To jest bardzo fajna odpowiedź stackoverflow.com/a/25250693/1586965
samthebest

Odpowiedzi:

107

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 Twraz z niejawnym parametrem typu Bound[T].

Rozważ metodę, tabulatektó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
}
Robert Harvey
źródło
145

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 „ Tma Manifest”. Przykład, do którego prowadzi link about Orderedvs, Orderingilustruje 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 ClassManifestuż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.

Ben Lings
źródło
2
„ma” zamiast „jest” lub „postrzegany jako” było dla mnie kluczowym spostrzeżeniem - nie widziałem tego w żadnym innym wyjaśnieniu. Posiadanie zwykłej angielskiej wersji nieco zagadkowych operatorów / funkcji znacznie ułatwia przyswajanie - dzięki!
DNA
1
@Ben Lings Co masz na myśli mówiąc… „ma” o typie …? O co chodzi z typem ?
jhegedus
1
@jhegedus Oto moja analiza: „o typie” oznacza, że ​​A odnosi się do typu. Wyrażenie „ma” jest często używane w projektowaniu zorientowanym obiektowo do opisu relacji między obiektami (np. Klient „ma„ Adres ”). Ale tutaj relacja „ma” dotyczy typów, a nie obiektów. To luźna analogia, ponieważ relacja „ma” nie jest nieodłączna ani uniwersalna, tak jak w projektowaniu obiektów obiektowych; Klient zawsze ma adres, ale dla kontekstu związanego A nie zawsze ma C. Raczej kontekst powiązany określa, że ​​instancja C [A] musi być dostarczona niejawnie.
jbyler
Uczę się Scala od miesiąca i to najlepsze wyjaśnienie, jakie widziałem w tym miesiącu! Dziękuję @Ben!
Lifu Huang
@Ben Lings: Dzięki, po spędzeniu tak długiego czasu na zrozumieniu, co jest związane z kontekstem, twoja odpowiedź jest bardzo pomocna. [Ma has adla mnie więcej sensu]
Shankar
39

(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 Fdo 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 Function1jest 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 
retronim
źródło
Czy mógłbyś wyjaśnić znaczenie #From w definicji f2? Nie jestem pewien, gdzie budowany jest typ F (czy powiedziałem to poprawnie?)
Collin,
1
Nazywa się to projekcją typu, odwołując się do elementu członkowskiego Fromtypu To[String]. Nie Frompodajemy 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].
retronim
czy powinno to być Function1 [T, String]?
ssanj
18

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 implicitlymetody lub nieco bardziej pomocnej contextmetody:

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) }
Aaron Novstrup
źródło