Jenis-jenis metode dependen, yang dulunya merupakan fitur eksperimental sebelumnya, kini telah diaktifkan secara default di trunk , dan tampaknya ini tampaknya telah menciptakan kegembiraan di komunitas Scala.
Pada pandangan pertama, tidak segera jelas apa manfaatnya. Heiko Seeberger memposting contoh sederhana tipe metode dependen di sini , yang seperti dapat dilihat di komentar di sana dapat dengan mudah direproduksi dengan parameter tipe pada metode. Jadi itu bukan contoh yang sangat menarik. (Saya mungkin kehilangan sesuatu yang jelas. Tolong perbaiki saya jika demikian.)
Apa saja contoh praktis dan berguna dari kasus penggunaan untuk tipe metode dependen di mana mereka jelas menguntungkan daripada alternatifnya?
Hal menarik apa yang bisa kita lakukan dengan mereka yang tidak mungkin / mudah sebelumnya?
Apa yang mereka beli dari kami atas fitur sistem tipe yang ada?
Juga, apakah tipe metode dependen dianalogikan atau menggambar inspirasi dari fitur apa pun yang ditemukan dalam sistem tipe bahasa mengetik canggih lainnya seperti Haskell, OCaml?
sumber
Jawaban:
Kurang lebih setiap penggunaan tipe anggota (mis. Bersarang) dapat menimbulkan kebutuhan akan tipe metode dependen. Secara khusus, saya berpendapat bahwa tanpa jenis metode dependen pola kue klasik lebih dekat menjadi anti-pola.
Jadi apa masalahnya? Jenis bersarang di Scala tergantung pada contoh terlampir mereka. Akibatnya, dengan tidak adanya tipe metode dependen, upaya untuk menggunakannya di luar contoh itu bisa sangat sulit. Ini dapat mengubah desain yang awalnya tampak elegan dan menarik menjadi monster yang mengerikan dan sulit untuk diperbaiki.
Saya akan menggambarkan bahwa dengan latihan yang saya berikan selama kursus pelatihan Advanced Scala saya ,
Ini adalah contoh dari pola kue klasik: kami memiliki keluarga abstraksi yang secara bertahap disempurnakan melalui hierarki (
ResourceManager
/Resource
disempurnakan olehFileManager
/File
yang pada gilirannya disempurnakan olehNetworkFileManager
/RemoteFile
). Ini contoh mainan, tetapi polanya nyata: digunakan di seluruh kompiler Scala dan digunakan secara luas di plugin Scala Eclipse.Berikut adalah contoh abstraksi yang digunakan,
Perhatikan bahwa dependensi jalur berarti bahwa kompilator akan menjamin bahwa
testHash
dantestDuplicates
metodeNetworkFileManager
hanya dapat dipanggil dengan argumen yang sesuai dengannya, yaitu. itu sendiriRemoteFiles
, dan tidak ada yang lain.Tidak dapat disangkal bahwa itu adalah properti yang diinginkan, tetapi misalkan kita ingin memindahkan kode uji ini ke file sumber yang berbeda? Dengan tipe metode dependen, mudah untuk mendefinisikan kembali metode-metode tersebut di luar
ResourceManager
hierarki,Perhatikan penggunaan tipe metode dependen di sini: jenis argumen kedua (
rm.Resource
) tergantung pada nilai argumen pertama (rm
).Dimungkinkan untuk melakukan ini tanpa tipe metode dependen, tetapi ini sangat canggung dan mekanismenya sangat tidak intuitif: Saya telah mengajar kursus ini selama hampir dua tahun sekarang, dan pada waktu itu, tidak ada seorang pun yang datang dengan solusi kerja tanpa alasan.
Cobalah sendiri ...
Setelah beberapa saat berjuang dengan itu Anda mungkin akan menemukan mengapa saya (atau mungkin itu adalah David MacIver, kita tidak dapat mengingat siapa di antara kita yang menciptakan istilah) menyebut ini Bakery of Doom.
Sunting: konsensus adalah bahwa Bakery of Doom adalah koin David MacIver ...
Sebagai bonus: bentuk Scala dari tipe dependen secara umum (dan tipe metode dependen sebagai bagian dari itu) terinspirasi oleh bahasa pemrograman Beta ... mereka muncul secara alami dari semantik bersarang Beta yang konsisten. Saya tidak tahu bahasa pemrograman arus utama apa pun yang memiliki tipe dependen dalam formulir ini. Bahasa seperti Coq, Cayenne, Epigram dan Agda memiliki bentuk pengetikan dependen berbeda yang dalam beberapa hal lebih umum, tetapi berbeda secara signifikan dengan menjadi bagian dari sistem tipe yang, tidak seperti Scala, tidak memiliki subtyping.
sumber
def testHash4[R <: ResourceManager#BasicResource](rm: ResourceManager { type Resource = R }, r: R) = assert(r.hash == "9e47088d")
Saya kira ini dapat dianggap sebagai bentuk lain dari tipe dependen.Di tempat lain, kami dapat secara statis menjamin bahwa kami tidak mencampuradukkan node dari dua grafik yang berbeda, misalnya:
Tentu saja, ini sudah berfungsi jika didefinisikan di dalam
Graph
, tetapi katakanlah kita tidak dapat memodifikasiGraph
dan sedang menulis ekstensi "pimp my library" untuk itu.Tentang pertanyaan kedua: tipe yang diaktifkan oleh fitur ini jauh lebih lemah daripada tipe dependen lengkap (Lihat Pemrograman Ketik Ketergantungan di Agda untuk mengetahui lebih lanjut tentang itu.) Saya rasa saya tidak pernah melihat analogi sebelumnya.
sumber
Fitur baru ini diperlukan ketika anggota tipe abstrak konkret digunakan sebagai ganti parameter tipe . Ketika parameter tipe digunakan, dependensi tipe polimorfisme keluarga dapat diekspresikan dalam Scala versi terbaru dan beberapa yang lebih lama, seperti dalam contoh sederhana berikut.
sumber
trait C {type A}; def f[M](a: C { type A = M}, b: M) = 0;class CI extends C{type A=Int};class CS extends C{type A=String}
dll.Saya sedang mengembangkan model untuk interoption dari bentuk pemrograman deklaratif dengan keadaan lingkungan. Detailnya tidak relevan di sini (mis. Detail tentang callback dan kesamaan konseptual dengan model Actor yang dikombinasikan dengan Serializer).
Masalah yang relevan adalah nilai negara disimpan dalam peta hash dan direferensikan oleh nilai kunci hash. Fungsi memasukkan argumen tidak berubah yang merupakan nilai dari lingkungan, dapat memanggil fungsi lain seperti itu, dan menulis status ke lingkungan. Tetapi fungsi tidak diperbolehkan untuk membaca nilai dari lingkungan (jadi kode internal fungsi tidak tergantung pada urutan perubahan negara dan dengan demikian tetap deklaratif dalam pengertian itu). Bagaimana cara mengetik ini di Scala?
Kelas lingkungan harus memiliki metode kelebihan beban yang menginput fungsi seperti itu untuk memanggil, dan input kunci hash dari argumen fungsi. Dengan demikian metode ini dapat memanggil fungsi dengan nilai-nilai yang diperlukan dari peta hash, tanpa menyediakan akses baca publik ke nilai-nilai (dengan demikian sebagaimana diperlukan, menolak fungsi kemampuan untuk membaca nilai dari lingkungan).
Tetapi jika kunci hash ini string atau nilai-nilai hash integer, mengetik statis jenis hash peta elemen subsumes ke Setiap atau AnyRef (hash kode peta tidak ditampilkan di bawah), dan dengan demikian run-time ketidakcocokan bisa terjadi, yaitu akan mungkin untuk menempatkan semua jenis nilai dalam peta hash untuk kunci hash yang diberikan.
Meskipun saya tidak menguji yang berikut ini, secara teori saya bisa mendapatkan kunci hash dari nama kelas saat mempekerjakan runtime
classOf
, jadi kunci hash adalah nama kelas, bukan string (menggunakan backticks Scala untuk menanamkan string dalam nama kelas).Jadi keamanan tipe statis tercapai.
sumber
def callit[A](argkeys: Tuple[DependentHashKey,DependentHashKey])(func: Env => argkeys._0.ValueType => argkeys._1.ValueType => A): A
. Kami tidak akan menggunakan kumpulan kunci argumen, karena tipe elemen akan dimasukkan (tidak diketahui pada saat kompilasi) dalam jenis koleksi.