Di Scala, kita dapat menggunakan setidaknya dua metode untuk retrofit tipe yang sudah ada atau yang baru. Misalkan kita ingin menyatakan bahwa sesuatu dapat dikuantifikasi menggunakan Int
. Kita dapat mendefinisikan sifat berikut.
Konversi implisit
trait Quantifiable{ def quantify: Int }
Dan kemudian kita dapat menggunakan konversi implisit untuk mengukur misalnya String dan Daftar.
implicit def string2quant(s: String) = new Quantifiable{
def quantify = s.size
}
implicit def list2quantifiable[A](l: List[A]) = new Quantifiable{
val quantify = l.size
}
Setelah mengimpor ini, kita dapat memanggil metode quantify
pada string dan daftar. Perhatikan bahwa daftar yang dapat diukur menyimpan panjangnya, sehingga menghindari traversal daftar yang mahal pada panggilan berikutnya ke quantify
.
Ketik kelas
Alternatifnya adalah dengan mendefinisikan "saksi" Quantified[A]
yang menyatakan, bahwa beberapa tipe A
dapat dikuantifikasi.
trait Quantified[A] { def quantify(a: A): Int }
Kami kemudian menyediakan contoh kelas jenis ini untuk String
dan di List
suatu tempat.
implicit val stringQuantifiable = new Quantified[String] {
def quantify(s: String) = s.size
}
Dan jika kita kemudian menulis metode yang perlu mengukur argumennya, kita menulis:
def sumQuantities[A](as: List[A])(implicit ev: Quantified[A]) =
as.map(ev.quantify).sum
Atau menggunakan sintaks terikat konteks:
def sumQuantities[A: Quantified](as: List[A]) =
as.map(implicitly[Quantified[A]].quantify).sum
Tetapi kapan harus menggunakan metode yang mana?
Sekarang pertanyaannya. Bagaimana saya bisa memutuskan di antara kedua konsep itu?
Apa yang saya perhatikan sejauh ini.
jenis kelas
- kelas tipe memungkinkan sintaks terikat konteks yang bagus
- dengan kelas tipe saya tidak membuat objek pembungkus baru pada setiap penggunaan
- sintaks konteks terikat tidak berfungsi lagi jika kelas tipe memiliki beberapa parameter tipe; bayangkan saya ingin mengukur hal-hal tidak hanya dengan bilangan bulat tetapi dengan nilai-nilai dari beberapa tipe umum
T
. Saya ingin membuat kelas tipeQuantified[A,T]
konversi implisit
- sejak saya membuat objek baru, saya dapat menyimpan nilai di sana atau menghitung representasi yang lebih baik; tetapi haruskah saya menghindari ini, karena ini mungkin terjadi beberapa kali dan konversi eksplisit mungkin hanya akan dipanggil sekali?
Apa yang saya harapkan dari sebuah jawaban
Sajikan satu (atau lebih) kasus penggunaan di mana perbedaan antara kedua konsep itu penting dan jelaskan mengapa saya lebih memilih salah satu daripada yang lain. Juga menjelaskan esensi dari kedua konsep dan hubungannya satu sama lain akan menyenangkan, bahkan tanpa contoh.
sumber
size
dari daftar nilai dan mengatakan bahwa ia menghindari traversal mahal dari daftar panggilan berikutnya untuk dihitung, tetapi dari setiap panggilan Anda kequantify
dalamlist2quantifiable
akan memicu lagi-lagi dengan demikian mengembalikanQuantifiable
dan menghitung ulangquantify
properti. Apa yang saya katakan adalah bahwa sebenarnya tidak ada cara untuk menyimpan hasil dengan konversi implisit.Jawaban:
Meskipun saya tidak ingin menduplikasi materi saya dari Scala In Depth , saya pikir perlu dicatat bahwa tipe kelas / sifat tipe jauh lebih fleksibel.
def foo[T: TypeClass](t: T) = ...
memiliki kemampuan untuk mencari lingkungan lokalnya untuk kelas tipe default. Namun, saya dapat mengganti perilaku default kapan saja dengan salah satu dari dua cara berikut:
Berikut contohnya:
def myMethod(): Unit = { // overrides default implicit for Int implicit object MyIntFoo extends Foo[Int] { ... } foo(5) foo(6) // These all use my overridden type class foo(7)(new Foo[Int] { ... }) // This one needs a different configuration }
Ini membuat kelas tipe jauh lebih fleksibel. Hal lain adalah bahwa jenis kelas / sifat mendukung pencarian implisit dengan lebih baik.
Dalam contoh pertama Anda, jika Anda menggunakan tampilan implisit, kompilator akan melakukan pencarian implisit untuk:
Function1[Int, ?]
Yang akan melihat
Function1
objek pendamping danInt
objek pendamping.Perhatikan bahwa
Quantifiable
tidak ada dalam pencarian implisit. Ini berarti Anda harus menempatkan tampilan implisit dalam objek paket atau mengimpornya ke dalam cakupan. Lebih banyak pekerjaan untuk mengingat apa yang sedang terjadi.Di sisi lain, kelas tipe bersifat eksplisit . Anda melihat apa yang dicari di tanda tangan metode. Anda juga memiliki pencarian implisit
Quantifiable[Int]
yang akan mencari di
Quantifiable
objek pendamping danInt
objek pendamping. Artinya Anda dapat memberikan default dan tipe baru (sepertiMyString
kelas) dapat menyediakan default dalam objek pendampingnya dan akan dicari secara implisit.Secara umum, saya menggunakan kelas tipe. Mereka jauh lebih fleksibel untuk contoh awal. Satu-satunya tempat saya menggunakan konversi implisit adalah saat menggunakan lapisan API antara pembungkus Scala dan pustaka Java, dan bahkan ini bisa 'berbahaya' jika Anda tidak berhati-hati.
sumber
Satu kriteria yang dapat diterapkan adalah bagaimana Anda ingin fitur baru "terasa"; menggunakan konversi implisit, Anda dapat membuatnya terlihat seperti metode lain:
"my string".newFeature
... saat menggunakan kelas tipe, akan selalu terlihat seperti Anda memanggil fungsi eksternal:
newFeature("my string")
Satu hal yang bisa Anda capai dengan kelas tipe dan bukan dengan konversi implisit adalah menambahkan properti ke sebuah tipe , daripada ke sebuah instance tipe. Anda kemudian dapat mengakses properti ini bahkan ketika Anda tidak memiliki contoh dari tipe yang tersedia. Contoh kanonisnya adalah:
trait Default[T] { def value : T } implicit object DefaultInt extends Default[Int] { def value = 42 } implicit def listsHaveDefault[T : Default] = new Default[List[T]] { def value = implicitly[Default[T]].value :: Nil } def default[T : Default] = implicitly[Default[T]].value scala> default[List[List[Int]]] resN: List[List[Int]] = List(List(42))
Contoh ini juga menunjukkan bagaimana konsep terkait erat: kelas tipe hampir tidak akan berguna jika tidak ada mekanisme untuk menghasilkan banyak instansinya secara tak terbatas; tanpa
implicit
metode (bukan konversi, harus diakui), saya hanya dapat memiliki banyak jenisDefault
properti.sumber
default
untuk pembaca mendatang.Anda dapat memikirkan perbedaan antara kedua teknik dengan analogi aplikasi fungsi, hanya dengan pembungkus bernama. Sebagai contoh:
trait Foo1[A] { def foo(a: A): Int } // analogous to A => Int trait Foo0 { def foo: Int } // analogous to Int
Sebuah instance dari yang pertama merangkum sebuah fungsi dari sebuah tipe
A => Int
, sedangkan sebuah instance dari yang terakhir telah diterapkan ke sebuahA
. Anda bisa melanjutkan polanya ...trait Foo2[A, B] { def foo(a: A, b: B): Int } // sort of like A => B => Int
sehingga Anda dapat berpikir
Foo1[B]
seperti aplikasi parsialFoo2[A, B]
untuk beberapaA
contoh. Sebuah contoh yang bagus tentang ini ditulis oleh Miles Sabin sebagai "Ketergantungan Fungsional di Scala" .Jadi sebenarnya maksud saya adalah, pada prinsipnya:
sumber