Saya membaca dan mendengar bahwa orang-orang (juga di situs ini) secara rutin memuji paradigma pemrograman fungsional, menekankan betapa baiknya memiliki segala sesuatu yang abadi. Khususnya, orang mengusulkan pendekatan ini bahkan dalam bahasa OO yang secara tradisional sangat penting, seperti C #, Java atau C ++, tidak hanya dalam bahasa yang murni fungsional seperti Haskell yang memaksakan ini pada programmer.
Saya merasa sulit untuk memahaminya, karena saya menemukan sifat berubah-ubah dan efek samping ... nyaman. Namun, mengingat bagaimana orang-orang saat ini mengutuk efek samping dan menganggapnya sebagai praktik yang baik untuk menyingkirkan mereka sedapat mungkin, saya percaya bahwa jika saya ingin menjadi programmer yang kompeten saya harus memulai jorney saya menuju pemahaman yang lebih baik tentang paradigma ... Oleh karena itu Q.
Satu tempat ketika saya menemukan masalah dengan paradigma fungsional adalah ketika suatu objek secara alami dirujuk dari banyak tempat. Biarkan saya menggambarkannya pada dua contoh.
Contoh pertama adalah game C # yang saya coba buat di waktu luang . Ini permainan web berbasis giliran di mana kedua pemain memiliki tim 4 monster dan dapat mengirim monster dari tim mereka ke medan perang, di mana ia akan menghadapi monster yang dikirim oleh pemain lawan. Pemain juga dapat memanggil kembali monster dari medan perang dan menggantinya dengan monster lain dari tim mereka (mirip dengan Pokemon).
Dalam pengaturan ini, monster tunggal dapat dirujuk secara alami dari setidaknya 2 tempat: tim pemain dan medan perang, yang merujuk dua monster "aktif".
Sekarang mari kita pertimbangkan situasi ketika satu monster terkena dan kehilangan 20 poin kesehatan. Dalam kurung paradigma imperatif saya memodifikasi health
bidang monster ini untuk mencerminkan perubahan ini - dan inilah yang saya lakukan sekarang. Namun, ini membuat Monster
kelas bisa berubah dan fungsi terkait (metode) tidak murni, yang saya kira dianggap praktik yang buruk seperti yang sekarang.
Meskipun saya memberi diri saya izin untuk memiliki kode permainan ini dalam keadaan kurang ideal untuk memiliki harapan untuk benar-benar menyelesaikannya di beberapa titik di masa depan, saya ingin tahu dan mengerti bagaimana seharusnya ditulis dengan benar. Karena itu: Jika ini cacat desain, bagaimana cara memperbaikinya?
Dalam gaya fungsional, seperti yang saya mengerti, saya akan membuat salinan dari Monster
objek ini , menjaganya agar tetap sama dengan yang lama kecuali untuk bidang yang satu ini; dan metode suffer_hit
akan mengembalikan objek baru ini alih-alih memodifikasi yang lama di tempat. Lalu aku juga akan menyalin Battlefield
objek, menjaga semua bidangnya tetap sama kecuali monster ini.
Ini hadir dengan setidaknya 2 kesulitan:
- Hirarki dapat dengan mudah menjadi lebih dalam dari contoh sederhana ini
Battlefield
->Monster
. Saya harus melakukan penyalinan semua bidang kecuali satu dan mengembalikan objek baru sepanjang hierarki ini. Ini akan menjadi kode boilerplate yang menurut saya menjengkelkan terutama karena pemrograman fungsional seharusnya mengurangi boilerplate. - Namun, masalah yang jauh lebih parah adalah bahwa hal ini akan menyebabkan data tidak sinkron . Monster aktif di lapangan akan melihat kesehatannya berkurang; Namun, monster yang sama ini, yang dirujuk dari pemain pengendali
Team
, tidak akan melakukannya. Jika saya sebaliknya menggunakan gaya imperatif, setiap modifikasi data akan langsung terlihat dari semua tempat kode lainnya dan dalam kasus seperti ini saya merasa sangat nyaman - tetapi cara saya mendapatkan hal-hal ini persis seperti yang dikatakan orang. salah dengan gaya imperatif!- Sekarang mungkin untuk mengurus masalah ini dengan melakukan perjalanan ke
Team
setelah setiap serangan. Ini pekerjaan ekstra. Namun, bagaimana jika monster tiba-tiba dapat direferensikan dari lebih banyak tempat? Bagaimana jika saya datang dengan kemampuan yang, misalnya, memungkinkan monster fokus pada monster lain yang belum tentu di lapangan (saya sebenarnya mempertimbangkan kemampuan seperti itu)? Apakah saya pasti ingat untuk melakukan perjalanan ke monster yang fokus segera setelah setiap serangan? Ini sepertinya bom waktu yang akan meledak ketika kode semakin kompleks, jadi saya pikir ini bukan solusi.
- Sekarang mungkin untuk mengurus masalah ini dengan melakukan perjalanan ke
Sebuah ide untuk solusi yang lebih baik datang dari contoh kedua saya, ketika saya menemukan masalah yang sama. Di akademisi kami disuruh menulis penerjemah bahasa desain kami sendiri di Haskell. (Ini juga bagaimana saya dipaksa untuk mulai memahami apa itu FP). Masalahnya muncul ketika saya menerapkan penutupan. Sekali lagi cakupan yang sama sekarang dapat direferensikan dari beberapa tempat: Melalui variabel yang menyimpan lingkup ini dan sebagai cakupan induk dari setiap cakupan bersarang! Jelas, jika suatu perubahan dilakukan pada ruang lingkup ini melalui referensi yang menunjuk padanya, perubahan ini juga harus terlihat melalui semua referensi lain.
Solusi yang saya datangi adalah untuk menetapkan setiap lingkup ID dan memegang kamus pusat semua cakupan di State
monad. Sekarang variabel hanya akan menyimpan ID dari lingkup mereka terikat, bukan lingkup itu sendiri, dan cakupan bersarang juga akan memegang ID dari lingkup induknya.
Saya kira pendekatan yang sama bisa dicoba dalam game pertarungan monster saya ... Fields dan tim tidak mereferensikan monster; mereka malah memegang ID monster yang disimpan di kamus monster pusat.
Namun, saya sekali lagi dapat melihat masalah dengan pendekatan ini yang mencegah saya menerimanya tanpa ragu-ragu sebagai solusi untuk masalah ini:
Sekali lagi ini adalah sumber kode boilerplate. Itu membuat one-liners tentu 3-liner: apa yang sebelumnya merupakan modifikasi satu-baris pada satu bidang sekarang membutuhkan (a) Mengambil objek dari kamus pusat (b) Membuat perubahan (c) Menyimpan objek baru ke kamus pusat. Juga, memegang id objek dan kamus pusat alih-alih memiliki referensi meningkatkan kompleksitas. Karena FP diiklankan untuk mengurangi kompleksitas dan kode boilerplate, ini mengisyaratkan saya salah melakukannya.
Saya juga akan menulis tentang masalah kedua yang tampaknya jauh lebih parah: Pendekatan ini memperkenalkan kebocoran memori . Objek yang tidak terjangkau biasanya adalah sampah yang dikumpulkan. Namun, objek yang disimpan dalam kamus pusat tidak boleh berupa sampah yang dikumpulkan, bahkan jika tidak ada objek yang dapat dijangkau merujuk ID khusus ini. Dan sementara pemrograman yang secara teoritis hati-hati dapat menghindari kebocoran memori (kita bisa berhati-hati untuk menghapus secara manual setiap objek dari kamus pusat setelah tidak diperlukan lagi), ini rawan kesalahan dan FP diiklankan untuk meningkatkan kebenaran program sehingga sekali lagi ini mungkin bukan cara yang benar.
Namun, saya menemukan pada waktunya bahwa itu tampaknya menjadi masalah yang terpecahkan. Java menyediakan WeakHashMap
yang dapat digunakan untuk menyelesaikan masalah ini. C # menyediakan fasilitas serupa - ConditionalWeakTable
- meskipun menurut dokumen itu dimaksudkan untuk digunakan oleh kompiler. Dan di Haskell kami memiliki System.Mem.Weak .
Apakah menyimpan kamus seperti itu merupakan solusi fungsional yang tepat untuk masalah ini atau apakah ada yang lebih sederhana yang tidak dapat saya lihat? Saya membayangkan bahwa jumlah kamus seperti itu dapat dengan mudah tumbuh dan buruk; jadi jika kamus ini seharusnya juga tidak berubah ini bisa berarti banyak parameter-passing atau, dalam bahasa yang mendukung itu, perhitungan monadik, karena kamus akan diadakan di monad (tapi sekali lagi saya membacanya dalam fungsi murni bahasa sebagai kode sesedikit mungkin harus monadik, sedangkan solusi kamus ini akan menempatkan hampir semua kode di dalam State
monad; yang sekali lagi membuat saya ragu apakah ini solusi yang tepat.)
Setelah beberapa pertimbangan saya pikir saya akan menambahkan satu pertanyaan lagi: Apa yang kita peroleh dengan membangun kamus seperti itu? Apa yang salah dengan pemrograman imperatif adalah, menurut banyak ahli, bahwa perubahan pada beberapa objek menyebar ke bagian kode lainnya. Untuk mengatasi masalah ini, objek seharusnya tidak dapat diubah - justru karena alasan ini, jika saya mengerti dengan benar, bahwa perubahan yang dilakukan padanya tidak boleh terlihat di tempat lain. Tapi sekarang saya khawatir tentang potongan kode lain yang beroperasi pada data yang sudah ketinggalan zaman sehingga saya membuat kamus pusat sehingga ... sekali lagi perubahan pada beberapa bagian kode menyebar ke bagian kode lainnya! Bukankah kita, karena itu, kembali ke gaya imperatif dengan semua kelemahannya, tetapi dengan kompleksitas tambahan?
Team
) dapat mengambil hasil dari pertempuran dan dengan demikian menyatakan monster dengan tuple (nomor pertempuran, ID entitas monster).