Apa yang dimaksud dengan "konteks terikat" di Scala?

115

Salah satu fitur baru Scala 2.8 adalah batas konteks. Apa itu konteks terikat dan di mana itu berguna?

Tentu saja saya mencari terlebih dahulu (dan menemukan ini misalnya ) tetapi saya tidak dapat menemukan informasi yang benar-benar jelas dan rinci.

Jesper
sumber
8
lihat juga ini untuk tur semua jenis batas: gist.github.com/257758/47f06f2f3ca47702b3a86c76a5479d096cb8c7ec
Arjan Blokzijl
2
Jawaban luar biasa ini membandingkan / mengontraskan batas konteks dan batas tampilan: stackoverflow.com/questions/4465948/…
Aaron Novstrup
Ini adalah jawaban yang sangat bagus stackoverflow.com/a/25250693/1586965
samthebest

Jawaban:

107

Apakah Anda menemukan artikel ini ? Ini mencakup fitur terikat konteks baru, dalam konteks peningkatan array.

Umumnya, parameter tipe dengan konteks terikat adalah dalam bentuk [T: Bound]; itu diperluas ke parameter tipe biasa Tbersama dengan parameter tipe implisit Bound[T].

Pertimbangkan metode tabulateyang membentuk larik dari hasil penerapan fungsi tertentu f pada rentang angka dari 0 hingga panjang tertentu. Sampai dengan Scala 2.7, tabulasi dapat ditulis sebagai berikut:

def tabulate[T](len: Int, f: Int => T) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}

Dalam Scala 2.8 hal ini tidak lagi memungkinkan, karena informasi runtime diperlukan untuk membuat representasi yang tepat Array[T]. Seseorang perlu memberikan informasi ini dengan meneruskan a ClassManifest[T]ke dalam metode sebagai parameter implisit:

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
}

Sebagai bentuk singkatan, konteks terikat dapat digunakan pada parameter tipe Tsebagai gantinya, memberikan:

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
sumber
145

Jawaban Robert mencakup detail teknis dari Batas Konteks. Saya akan memberikan interpretasi saya tentang artinya.

Dalam Scala, sebuah View Bound ( A <% B) menangkap konsep 'dapat dilihat sebagai' (sedangkan batas atas <:menangkap konsep 'adalah a'). Konteks terikat ( A : C) mengatakan 'memiliki' tentang suatu tipe. Anda dapat membaca contoh tentang manifes sebagai " Tmemiliki Manifest". Contoh yang Anda tautkan ke tentang Orderedvs Orderingmenggambarkan perbedaannya. Sebuah metode

def example[T <% Ordered[T]](param: T)

mengatakan bahwa parameter dapat dilihat sebagai file Ordered. Dibandingkan dengan

def example[T : Ordering](param: T)

yang mengatakan bahwa parameter memiliki keterkaitan Ordering.

Dalam hal penggunaan, perlu waktu beberapa saat agar konvensi dapat ditetapkan, tetapi batas konteks lebih disukai daripada batas tampilan ( batas tampilan sekarang tidak digunakan lagi ). Salah satu saran adalah bahwa konteks terikat lebih disukai ketika Anda perlu mentransfer definisi implisit dari satu ruang lingkup ke lingkup lain tanpa perlu merujuknya secara langsung (ini tentu saja kasus yang ClassManifestdigunakan untuk membuat larik).

Cara lain untuk berpikir tentang batas tampilan dan batas konteks adalah bahwa yang pertama mentransfer konversi implisit dari cakupan pemanggil. Transfer kedua objek implisit dari lingkup pemanggil.

Ben Lings
sumber
2
"memiliki" daripada "adalah" atau "dilihat sebagai" adalah wawasan utama bagi saya - tidak terlihat dalam penjelasan lain. Memiliki versi bahasa Inggris yang sederhana dari operator / fungsi yang sedikit samar membuatnya lebih mudah untuk diserap - terima kasih!
DNA
1
@ Ben Lings Apa yang Anda maksud dengan .... 'memiliki' tentang suatu tipe ...? Apa tentang tipe ?
jhegedus
1
@jhegedus Berikut uraian saya: "about a type" berarti A merujuk ke sebuah tipe. Frase "memiliki" sering digunakan dalam desain berorientasi objek untuk menggambarkan hubungan objek (misalnya Pelanggan "memiliki" Alamat). Tapi di sini hubungan "memiliki" adalah antar tipe, bukan objek. Ini analogi yang longgar karena hubungan "memiliki" tidak melekat atau universal seperti dalam desain OO; Pelanggan selalu memiliki Alamat tetapi untuk konteks terikat A tidak selalu memiliki C. Sebaliknya, terikat konteks menentukan bahwa turunan C [A] harus disediakan secara implisit.
jbyler
Saya telah mempelajari Scala selama sebulan, dan ini adalah penjelasan terbaik yang saya lihat di bulan ini! Terima kasih @Ben!
Lifu Huang
@Ben Lings: Terima kasih, setelah menghabiskan begitu lama untuk memahami apa yang terikat konteks, jawaban Anda sangat membantu. [ has aLebih masuk akal bagi saya]
Shankar
39

(Ini adalah catatan dalam tanda kurung. Baca dan pahami jawaban lainnya terlebih dahulu.)

Context Bounds sebenarnya menggeneralisasi View Bounds.

Jadi, diberikan kode ini yang diekspresikan dengan View Bound:

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

Ini juga bisa diekspresikan dengan Context Bound, dengan bantuan alias tipe yang mewakili fungsi dari tipe Fke tipe 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

Konteks terikat harus digunakan dengan tipe konstruktor * => *. Namun konstruktor tipe Function1adalah dari jenis (*, *) => *. Penggunaan alias tipe menerapkan sebagian parameter tipe kedua dengan tipe String, menghasilkan konstruktor tipe dari jenis yang tepat untuk digunakan sebagai terikat konteks.

Ada sebuah proposal yang memungkinkan Anda untuk secara langsung mengekspresikan tipe yang diterapkan sebagian di Scala, tanpa menggunakan alias tipe di dalam sebuah ciri. Anda kemudian bisa menulis:

def f3[T : [X](X => String)](t: T) = 0 
retronim
sumber
Bisakah Anda menjelaskan arti #From dalam definisi f2? Saya tidak yakin di mana tipe F akan dibangun (apakah saya mengatakan ini dengan benar?)
Collin
1
Ini disebut proyeksi tipe, mereferensikan anggota tipe Fromdari tipe tersebut To[String]. Kami tidak menyediakan argumen tipe ke From, jadi kami merujuk ke konstruktor tipe, bukan tipe. Konstruktor tipe ini adalah jenis yang tepat untuk digunakan sebagai konteks terikat - * -> *. Ini membatasi parameter tipe Tdengan meminta parameter tipe implisit To[String]#From[T]. Perluas alias tipe, dan voila, Anda tersisa Function1[String, T].
retronim
haruskah itu menjadi Function1 [T, String]?
ssanj
18

Ini adalah catatan dalam tanda kurung lainnya.

Seperti yang ditunjukkan Ben , konteks terikat mewakili batasan "has-a" antara parameter tipe dan kelas tipe. Dengan kata lain, ini mewakili batasan bahwa nilai implisit dari kelas tipe tertentu ada.

Saat menggunakan ikatan konteks, seseorang sering kali perlu memunculkan nilai implisit itu. Misalnya, dengan adanya batasan T : Ordering, seseorang akan sering membutuhkan contoh Ordering[T]yang memenuhi batasan tersebut. Seperti yang ditunjukkan di sini , Anda dapat mengakses nilai implisit dengan menggunakan implicitlymetode atau metode yang sedikit lebih membantu context:

def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) = 
   xs zip ys map { t => implicitly[Numeric[T]].times(t._1, t._2) }

atau

def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) =
   xs zip ys map { t => context[T]().times(t._1, t._2) }
Aaron Novstrup
sumber