Secara sederhana, apa konteks dan batas tampilan dan apa perbedaan di antara mereka?
Beberapa contoh yang mudah diikuti juga bagus!
Saya pikir ini sudah ditanyakan, tetapi, jika demikian, pertanyaannya tidak jelas di bilah "terkait". Jadi begini:
Sebuah pandangan terikat adalah mekanisme diperkenalkan pada Scala untuk memungkinkan penggunaan beberapa jenis A
seperti itu adalah beberapa jenis B
. Sintaks khasnya adalah ini:
def f[A <% B](a: A) = a.bMethod
Dengan kata lain, A
konversi tersirat harus B
tersedia, sehingga seseorang dapat memanggil B
metode pada objek bertipe A
. Penggunaan batas tampilan yang paling umum di pustaka standar (sebelum Scala 2.8.0), adalah dengan Ordered
, seperti ini:
def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b
Karena seseorang dapat mengkonversi A
menjadi Ordered[A]
, dan karena Ordered[A]
mendefinisikan metode <(other: A): Boolean
, saya dapat menggunakan ekspresi a < b
.
Perlu diketahui bahwa batas tampilan sudah usang , Anda harus menghindarinya.
Batas konteks diperkenalkan dalam Scala 2.8.0, dan biasanya digunakan dengan apa yang disebut pola tipe kelas , pola kode yang mengemulasi fungsionalitas yang disediakan oleh kelas tipe Haskell, meskipun dengan cara yang lebih verbose.
Sementara batas tampilan dapat digunakan dengan tipe sederhana (misalnya, A <% String
), batas konteks membutuhkan tipe parameter , seperti di Ordered[A]
atas, tetapi tidak seperti itu String
.
Batas konteks menjelaskan nilai implisit , alih-alih konversi implisit tampilan terikat . Ini digunakan untuk menyatakan bahwa untuk beberapa tipe A
, ada nilai implisit dari tipe yang B[A]
tersedia. Sintaksnya seperti ini:
def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]
Ini lebih membingungkan daripada pandangan terikat karena tidak segera jelas bagaimana menggunakannya. Contoh umum penggunaan dalam Scala adalah ini:
def f[A : ClassManifest](n: Int) = new Array[A](n)
Sebuah Array
inisialisasi pada jenis parameter membutuhkan ClassManifest
akan tersedia, karena alasan misterius yang berkaitan dengan penghapusan jenis dan sifat non-penghapusan array.
Contoh lain yang sangat umum di perpustakaan sedikit lebih kompleks:
def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)
Di sini, implicitly
digunakan untuk mengambil kembali nilai implisit yang kita inginkan, salah satu tipe Ordering[A]
, yang kelas mendefinisikan metode compare(a: A, b: A): Int
.
Kita akan melihat cara lain untuk melakukan ini di bawah.
Seharusnya tidak mengejutkan bahwa batas tampilan dan batas konteks diimplementasikan dengan parameter implisit, mengingat definisi mereka. Sebenarnya, sintaks yang saya tunjukkan adalah gula sintaksis untuk apa yang sebenarnya terjadi. Lihat cara mereka mengurangi gula:
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)
Jadi, secara alami, seseorang dapat menulisnya dalam sintaksis lengkapnya, yang secara khusus berguna untuk batasan konteks:
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)
Batasan tampilan digunakan sebagian besar untuk mengambil keuntungan dari mucikari pola pustaka saya , yang melaluinya orang "menambahkan" metode ke kelas yang ada, dalam situasi di mana Anda ingin mengembalikan tipe asli entah bagaimana. Jika Anda tidak perlu mengembalikan jenis itu dengan cara apa pun, maka Anda tidak perlu terikat tampilan.
Contoh klasik penggunaan terikat tampilan adalah penanganan Ordered
. Perhatikan bahwa Int
tidak Ordered
, misalnya, meskipun ada konversi implisit. Contoh yang diberikan sebelumnya memerlukan tampilan terikat karena mengembalikan tipe yang tidak dikonversi:
def f[A <% Ordered[A]](a: A, b: A): A = if (a < b) a else b
Contoh ini tidak akan berfungsi tanpa batasan tampilan. Namun, jika saya harus mengembalikan tipe lain, maka saya tidak perlu lagi terikat tampilan:
def f[A](a: Ordered[A], b: A): Boolean = a < b
Konversi di sini (jika perlu) terjadi sebelum saya meneruskan parameter ke f
, jadi f
tidak perlu tahu tentang hal itu.
Selain itu Ordered
, penggunaan paling umum dari perpustakaan adalah penanganan String
dan Array
, yang merupakan kelas Java, seperti mereka koleksi Scala. Sebagai contoh:
def f[CC <% Traversable[_]](a: CC, b: CC): CC = if (a.size < b.size) a else b
Jika seseorang mencoba melakukan ini tanpa batasan tampilan, tipe kembalinya a String
akan menjadi WrappedString
(Scala 2.8), dan demikian pula untuk Array
.
Hal yang sama terjadi bahkan jika tipe hanya digunakan sebagai parameter tipe dari tipe kembali:
def f[A <% Ordered[A]](xs: A*): Seq[A] = xs.toSeq.sorted
Batas konteks terutama digunakan dalam apa yang kemudian dikenal sebagai pola typeclass , sebagai referensi ke kelas tipe Haskell. Pada dasarnya, pola ini mengimplementasikan alternatif pewarisan dengan menyediakan fungsionalitas melalui semacam pola adaptor implisit.
Contoh klasik adalah Scala 2.8 Ordering
, yang diganti di Ordered
seluruh perpustakaan Scala. Penggunaannya adalah:
def f[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a else b
Meskipun Anda biasanya akan melihat yang ditulis seperti ini:
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = {
import ord.mkOrderingOps
if (a < b) a else b
}
Yang mengambil keuntungan dari beberapa konversi implisit di dalam Ordering
yang memungkinkan gaya operator tradisional. Contoh lain dalam Scala 2.8 adalah Numeric
:
def f[A : Numeric](a: A, b: A) = implicitly[Numeric[A]].plus(a, b)
Contoh yang lebih kompleks adalah penggunaan koleksi baru CanBuildFrom
, tetapi sudah ada jawaban yang sangat panjang tentang itu, jadi saya akan menghindarinya di sini. Dan, seperti yang disebutkan sebelumnya, ada ClassManifest
penggunaannya, yang diperlukan untuk menginisialisasi array baru tanpa tipe beton.
Konteks terikat dengan pola typeclass jauh lebih mungkin untuk digunakan oleh kelas Anda sendiri, karena mereka memungkinkan pemisahan masalah, sedangkan batas tampilan dapat dihindari dalam kode Anda sendiri dengan desain yang baik (ini digunakan terutama untuk berkeliling desain orang lain ).
Meskipun sudah mungkin untuk waktu yang lama, penggunaan batas konteks telah benar-benar lepas landas pada tahun 2010, dan sekarang ditemukan pada tingkat tertentu di sebagian besar perpustakaan dan kerangka kerja paling penting Scala. Namun, contoh paling ekstrem dari penggunaannya adalah perpustakaan Scalaz, yang membawa banyak kekuatan Haskell ke Scala. Saya sarankan membaca tentang pola typeclass untuk lebih mengenal semua cara yang dapat digunakan.
EDIT
Pertanyaan terkait yang menarik: