Saya sedang mengembangkan aplikasi GUI, banyak bekerja dengan grafik - Anda dapat menganggapnya sebagai editor vektor, sebagai contoh. Sangat menggoda untuk membuat semua struktur data tidak berubah - sehingga saya bisa membatalkan / mengulang, menyalin / menempel, dan banyak hal lainnya hampir tanpa usaha.
Demi kesederhanaan, saya akan menggunakan contoh berikut - aplikasi digunakan untuk mengedit bentuk poligon, jadi saya memiliki objek "Poligon", yang hanya daftar poin-poin yang tidak dapat diubah:
Scene -> Polygon -> Point
Dan jadi saya hanya punya satu variabel yang bisa berubah-ubah dalam program saya - variabel yang menampung objek Scene saat ini. Masalah yang saya mulai ketika saya mencoba menerapkan menyeret titik - dalam versi bisa berubah, saya hanya mengambil Point
objek dan mulai memodifikasi koordinatnya. Dalam versi abadi - saya macet. Saya bisa menyimpan indeks Polygon
saat ini Scene
, indeks titik yang diseret Polygon
, dan menggantinya setiap waktu. Tetapi pendekatan ini tidak skala - ketika tingkat komposisi pergi ke 5 dan lebih jauh, boilerplate akan menjadi tak tertahankan.
Saya yakin masalah ini dapat diselesaikan - lagipula, ada Haskell dengan struktur yang sepenuhnya tidak dapat diubah dan IO monad. Tapi saya tidak bisa menemukan caranya.
Bisakah Anda memberi saya petunjuk?
sumber
Jawaban:
Anda memang benar, pendekatan ini tidak berskala jika Anda tidak bisa menyiasati boilerplate . Secara khusus, pelat untuk membuat Adegan baru dengan bagian kecil berubah. Namun, banyak bahasa fungsional menyediakan konstruksi untuk menangani manipulasi struktur bersarang semacam ini: lensa.
Lensa pada dasarnya adalah pengambil dan penyetel untuk data tidak berubah. Lensa memiliki fokus pada beberapa bagian kecil dari struktur yang lebih besar. Diberikan lensa, ada dua hal yang dapat Anda lakukan dengannya - Anda dapat melihat sebagian kecil dari nilai struktur yang lebih besar, atau Anda dapat mengatur bagian kecil dari nilai struktur yang lebih besar ke nilai baru. Misalnya, Anda memiliki lensa yang berfokus pada item ketiga dalam daftar:
Jenis itu berarti struktur yang lebih besar adalah daftar hal-hal, dan bagian kecil adalah salah satunya. Dengan lensa ini, Anda dapat melihat dan mengatur item ketiga dalam daftar:
Alasan lensa berguna adalah karena mereka adalah nilai yang mewakili getter dan setter, dan Anda dapat mengabstraksikan mereka dengan cara yang sama seperti Anda dapat nilai lainnya. Anda dapat membuat fungsi yang mengembalikan lensa, misalnya
listItemLens
fungsi yang mengambil angkan
dan mengembalikan lensa yang melihatn
item ke dalam daftar. Selain itu, lensa dapat disusun :Setiap lensa merangkum perilaku untuk melintasi satu tingkat struktur data. Dengan menggabungkannya, Anda dapat menghilangkan pelat ketel untuk melintasi beberapa tingkat struktur kompleks. Misalnya, seandainya Anda memiliki
scenePolygonLens i
yang melihati
Poligon ke dalam suatu Scene, danpolygonPointLens n
yang melihatnth
Titik dalam Poligon, Anda dapat membuat konstruktor lensa untuk fokus pada titik tertentu yang Anda pedulikan di seluruh adegan seperti:Sekarang anggaplah pengguna mengklik titik 3 poligon 14 dan memindahkannya 10 piksel ke kanan. Anda dapat memperbarui adegan Anda seperti:
Ini dengan baik berisi semua pelat untuk melintasi dan memperbarui adegan di dalamnya
lens
, yang perlu Anda perhatikan adalah apa yang ingin Anda ubah titiknya. Anda dapat lebih lanjut mengabstraksi ini denganlensTransform
fungsi yang menerima lensa, target, dan fungsi untuk memperbarui tampilan target melalui lensa:Ini mengambil fungsi dan mengubahnya menjadi "pembaru" pada struktur data yang rumit, menerapkan fungsi hanya pada tampilan dan menggunakannya untuk membangun tampilan baru. Jadi kembali ke skenario memindahkan titik ke-3 dari poligon ke-14 ke 10 piksel yang tepat, yang dapat dinyatakan dalam bentuk
lensTransform
seperti ini:Dan hanya itu yang Anda butuhkan untuk memperbarui seluruh adegan. Ini adalah ide yang sangat kuat dan bekerja dengan sangat baik ketika Anda memiliki beberapa fungsi yang bagus untuk membuat lensa yang melihat bagian-bagian dari data yang Anda pedulikan.
Namun ini semua sangat luar biasa saat ini, bahkan di komunitas pemrograman fungsional. Sulit untuk menemukan dukungan perpustakaan yang baik untuk bekerja dengan lensa, dan bahkan lebih sulit untuk menjelaskan bagaimana mereka bekerja dan apa manfaatnya bagi rekan kerja Anda. Ambil pendekatan ini dengan sebutir garam.
sumber
Saya telah mengerjakan masalah yang persis sama (tetapi hanya dengan 3 level komposisi). Ide dasarnya adalah untuk mengkloning, lalu memodifikasi . Dalam gaya pemrograman yang tidak berubah, kloning dan modifikasi harus terjadi bersama, yang menjadi objek perintah .
Perhatikan bahwa dalam gaya pemrograman yang bisa berubah, kloning akan tetap diperlukan:
Dalam gaya pemrograman yang bisa berubah,
Dalam gaya pemrograman abadi,
sumber
Objek yang sangat abadi memiliki keuntungan bahwa mengkloning sesuatu secara sederhana hanya membutuhkan menyalin referensi. Mereka memiliki kelemahan yang bahkan membuat perubahan kecil ke objek bersarang sangat membutuhkan membangun contoh baru dari setiap objek di mana ia bersarang. Objek yang dapat berubah memiliki keuntungan bahwa mengubah suatu objek itu mudah - lakukan saja - tetapi mengkloning mendalam suatu objek membutuhkan membangun objek baru yang berisi klon yang dalam dari setiap objek yang bersarang. Lebih buruk lagi, jika seseorang ingin mengkloning suatu objek dan membuat perubahan, mengkloning objek itu, membuat perubahan lain, dll. Maka tidak peduli seberapa besar atau kecil perubahan yang dilakukan, seseorang harus menyimpan salinan seluruh hierarki untuk setiap versi yang disimpan dari keadaan objek. Menjijikan.
Suatu pendekatan yang mungkin layak dipertimbangkan adalah untuk mendefinisikan tipe abstrak "mungkinMutable" dengan tipe turunan yang dapat berubah dan sangat tidak dapat diubah. Semua tipe seperti itu akan menampilkan
AsImmutable
metode; memanggil metode itu pada instance objek yang sangat tidak dapat diubah hanya akan mengembalikan instance itu. Menyebutnya pada instance yang bisa berubah akan mengembalikan instance yang sangat tidak dapat diubah yang propertinya adalah snapshot yang sangat tidak dapat diubah dari padanannya dalam aslinya. Jenis yang tidak dapat diubah dengan padanan yang dapat berubah akan menggunakanAsMutable
metode, yang akan membangun contoh yang dapat berubah yang propertinya cocok dengan yang asli.Mengubah objek yang bersarang di objek yang sangat tidak dapat diubah akan memerlukan terlebih dahulu mengganti objek yang tidak bisa diubah dengan yang bisa berubah, kemudian mengganti properti yang mengandung benda yang akan diubah dengan benda yang bisa berubah, dll. Tetapi membuat perubahan berulang pada aspek yang sama dari objek objek keseluruhan tidak akan memerlukan membuat objek tambahan sampai saat upaya dilakukan untuk memanggil
AsImmutable
objek yang bisa berubah (yang akan membuat objek yang bisa berubah bisa berubah, tetapi mengembalikan objek yang tidak dapat diubah yang memegang data yang sama).Sebagai optimalisasi yang sederhana namun signifikan, setiap objek yang dapat berubah dapat menyimpan referensi yang di-cache ke objek dari tipe yang terkait yang tidak dapat diubah, dan masing-masing tipe yang tidak berubah harus menyimpan cache
GetHashCode
nilainya. Saat memanggilAsImmutable
objek yang bisa berubah, sebelum mengembalikan objek yang tidak bisa diubah baru, periksa apakah itu cocok dengan referensi yang di-cache. Jika demikian, kembalikan referensi yang di-cache (meninggalkan objek yang tidak dapat diubah baru). Kalau tidak, perbarui referensi yang di-cache untuk menampung objek baru dan mengembalikannya. Jika ini dilakukan, panggilan berulang keAsImmutable
tanpa mutasi yang mengintervensi akan menghasilkan referensi objek yang sama. Bahkan jika seseorang tidak menghemat biaya pembuatan instance baru, ia akan menghindari biaya memori untuk menyimpannya. Selanjutnya, perbandingan kesetaraan antara objek yang tidak dapat diubah dapat sangat dipercepat jika dalam banyak kasus item yang dibandingkan adalah referensi-sama atau memiliki kode hash yang berbeda.sumber