Apa konteks Scala dan batas tampilan?

267

Secara sederhana, apa konteks dan batas tampilan dan apa perbedaan di antara mereka?

Beberapa contoh yang mudah diikuti juga bagus!

chrsan
sumber

Jawaban:

477

Saya pikir ini sudah ditanyakan, tetapi, jika demikian, pertanyaannya tidak jelas di bilah "terkait". Jadi begini:

Apa itu Batas Tampilan?

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, Akonversi tersirat harus Btersedia, sehingga seseorang dapat memanggil Bmetode 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 Amenjadi 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.

Apa itu Batas Konteks?

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 Arrayinisialisasi pada jenis parameter membutuhkan ClassManifestakan 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, implicitlydigunakan 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.

Bagaimana Lihat Batas dan Batas Konteks diterapkan?

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)

Untuk apa Bound View digunakan?

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 Inttidak 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 ftidak perlu tahu tentang hal itu.

Selain itu Ordered, penggunaan paling umum dari perpustakaan adalah penanganan Stringdan 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 Stringakan 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

Untuk apa Bound Konteks digunakan?

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 Orderedseluruh 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 Orderingyang 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 ClassManifestpenggunaannya, 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:

Daniel C. Sobral
sumber
9
Terima kasih banyak. Saya tahu ini telah dijawab sebelumnya, dan mungkin saya belum cukup membaca saat itu, tetapi penjelasan Anda di sini adalah yang paling jelas yang pernah saya lihat. Jadi terima kasih lagi.
chrsan
3
@ chrsan Saya menambahkan dua bagian lagi, membahas lebih rinci di mana masing-masing menggunakan satu.
Daniel C. Sobral
2
Saya pikir ini adalah penjelasan yang bagus. Saya ingin menerjemahkan ini untuk blog bahasa Jerman saya (dgronau.wordpress.com) jika Anda setuju.
Landei
3
Sejauh ini, inilah penjelasan terbaik dan terlengkap dari topik ini yang saya temukan sejauh ini. Terima kasih sekali!
fotNelton
2
Sooo, kapan buku Scala Anda keluar, dan di mana saya bisa membelinya :)
wfbarksdale