Saya menyaksikan pembicaraan Stuart Sierra " Thinking In Data " dan mengambil salah satu ide darinya sebagai prinsip desain dalam game yang saya buat ini. Perbedaannya adalah dia bekerja di Clojure dan saya bekerja di JavaScript. Saya melihat beberapa perbedaan utama antara bahasa kami dalam hal:
- Clojure adalah pemrograman yang fungsional secara idiomatis
- Kebanyakan negara tidak berubah
Saya mengambil ide dari slide "Semuanya adalah Peta" (Dari 11 menit, 6 detik hingga> 29 menit). Beberapa hal yang dia katakan adalah:
- Setiap kali Anda melihat fungsi yang membutuhkan 2-3 argumen, Anda bisa membuat case untuk mengubahnya menjadi peta dan hanya meneruskan peta. Ada banyak keuntungan untuk itu:
- Anda tidak perlu khawatir tentang urutan argumen
- Anda tidak perlu khawatir tentang informasi tambahan apa pun. Jika ada kunci tambahan, itu bukan urusan kami. Mereka mengalir begitu saja, mereka tidak ikut campur.
- Anda tidak perlu mendefinisikan skema
- Berlawanan dengan mengirimkan Obyek, tidak ada data yang disembunyikan. Tapi, dia membuat kasus bahwa persembunyian data dapat menyebabkan masalah dan dinilai terlalu tinggi:
- Performa
- Kemudahan implementasi
- Segera setelah Anda berkomunikasi melalui jaringan atau lintas proses, Anda harus tetap memiliki kedua belah pihak menyetujui representasi data. Itu pekerjaan ekstra yang bisa Anda lewati jika Anda hanya bekerja pada data.
Paling relevan dengan pertanyaan saya. Ini adalah 29 menit dalam: "Jadikan fungsi Anda dapat dikompilasi". Berikut contoh kode yang ia gunakan untuk menjelaskan konsep:
;; Bad (defn complex-process [] (let [a (get-component @global-state) b (subprocess-one a) c (subprocess-two a b) d (subprocess-three a b c)] (reset! global-state d))) ;; Good (defn complex-process [state] (-> state subprocess-one subprocess-two subprocess-three))
Saya mengerti sebagian besar programmer tidak terbiasa dengan Clojure, jadi saya akan menulis ulang ini dengan gaya imperatif:
;; Good def complex-process(State state) state = subprocess-one(state) state = subprocess-two(state) state = subprocess-three(state) return state
Inilah kelebihannya:
- Mudah diuji
- Mudah untuk melihat fungsi-fungsi tersebut secara terpisah
- Mudah mengomentari satu baris ini dan melihat apa hasilnya dengan menghapus satu langkah
- Setiap subproses dapat menambahkan lebih banyak informasi ke negara. Jika subproses seseorang perlu mengkomunikasikan sesuatu ke subproses tiga, itu semudah menambahkan kunci / nilai.
- Tidak ada boilerplate untuk mengekstraksi data yang Anda butuhkan dari negara hanya agar Anda dapat menyimpannya kembali. Cukup lewati seluruh negara bagian dan biarkan subproses menetapkan apa yang dibutuhkan.
Sekarang, kembali ke situasi saya: Saya mengambil pelajaran ini dan menerapkannya pada permainan saya. Artinya, hampir semua fungsi tingkat tinggi saya mengambil dan mengembalikan gameState
objek. Objek ini berisi semua data permainan. EG: Daftar badGuys, daftar menu, jarahan di tanah, dll. Berikut adalah contoh fungsi pembaruan saya:
update(gameState)
...
gameState = handleUnitCollision(gameState)
...
gameState = handleLoot(gameState)
...
Yang ingin saya tanyakan di sini adalah, sudahkah saya membuat beberapa kekejian yang memutarbalikkan ide yang hanya praktis dalam bahasa pemrograman fungsional? JavaScript tidak berfungsi secara idiomatis (meskipun dapat ditulis seperti itu) dan sangat sulit untuk menulis struktur data yang tidak dapat diubah. Satu hal yang mengkhawatirkan saya adalah dia berasumsi bahwa masing-masing subproses itu murni. Mengapa asumsi itu perlu dibuat? Jarang ada fungsi saya yang murni (maksud saya, mereka sering memodifikasi gameState
. Saya tidak punya efek samping rumit lain selain itu). Apakah ide-ide ini berantakan jika Anda tidak memiliki data abadi?
Saya khawatir bahwa suatu hari saya akan bangun dan menyadari bahwa seluruh desain ini palsu dan saya benar-benar baru saja menerapkan anti-pola Big Ball Of Mud .
Sejujurnya, saya telah mengerjakan kode ini selama berbulan-bulan dan ini sangat bagus. Saya merasa seperti saya mendapatkan semua keuntungan yang diklaimnya. Kode saya sangat mudah untuk saya pikirkan. Tapi saya tim satu orang jadi saya memiliki kutukan pengetahuan.
Memperbarui
Saya telah mengkode 6+ bulan dengan pola ini. Biasanya saat ini saya lupa apa yang telah saya lakukan dan di situlah "apakah saya menulis ini dengan cara yang bersih?" ikut bermain. Jika tidak, saya akan benar-benar berjuang. Sejauh ini, saya tidak berjuang sama sekali.
Saya mengerti bagaimana set mata lain diperlukan untuk memvalidasi perawatannya. Yang bisa saya katakan adalah saya peduli tentang rawatan pertama dan terutama. Saya selalu menjadi penginjil paling keras untuk kode bersih di mana pun saya bekerja.
Saya ingin membalas langsung kepada mereka yang sudah memiliki pengalaman pribadi yang buruk dengan cara pengkodean ini. Saya tidak mengetahuinya saat itu, tetapi saya pikir kita benar-benar berbicara tentang dua cara penulisan kode yang berbeda. Cara saya melakukannya tampaknya lebih terstruktur daripada apa yang orang lain alami. Ketika seseorang memiliki pengalaman pribadi yang buruk dengan "Semuanya adalah peta" mereka berbicara tentang betapa sulitnya mempertahankan karena:
- Anda tidak pernah tahu struktur peta yang membutuhkan fungsi
- Fungsi apa pun dapat mengubah input dengan cara yang tidak pernah Anda harapkan. Anda harus melihat seluruh basis kode untuk mencari tahu bagaimana kunci tertentu masuk ke peta atau mengapa itu menghilang.
Bagi mereka yang memiliki pengalaman seperti itu, mungkin basis kode adalah, "Semuanya membutuhkan 1 dari N jenis peta." Milik saya adalah, "Semuanya membutuhkan 1 dari 1 jenis peta". Jika Anda tahu struktur 1 tipe itu, Anda tahu struktur segalanya. Tentu saja, struktur itu biasanya tumbuh seiring waktu. Itu sebabnya ...
Ada satu tempat untuk mencari implementasi referensi (yaitu: skema). Implementasi referensi ini adalah kode yang digunakan game agar tidak ketinggalan zaman.
Adapun poin kedua, saya tidak menambahkan / menghapus kunci ke peta di luar implementasi referensi, saya hanya bermutasi apa yang sudah ada. Saya juga memiliki serangkaian besar tes otomatis.
Jika arsitektur ini akhirnya runtuh karena bobotnya sendiri, saya akan menambahkan pembaruan kedua. Kalau tidak, anggap semuanya berjalan dengan baik :)
sumber
Jawaban:
Saya telah mendukung aplikasi di mana 'semuanya adalah peta' sebelumnya. Itu ide yang buruk. TOLONG jangan lakukan itu!
Saat Anda menentukan argumen yang diteruskan ke fungsi, itu membuatnya sangat mudah untuk mengetahui nilai apa yang dibutuhkan fungsi. Ini menghindari melewati data asing ke fungsi yang hanya mengganggu programmer - setiap nilai yang disahkan menyiratkan bahwa itu diperlukan, dan itu membuat programmer yang mendukung kode Anda harus mencari tahu mengapa data diperlukan.
Di sisi lain, jika Anda melewatkan semuanya sebagai peta, programmer yang mendukung aplikasi Anda harus sepenuhnya memahami fungsi yang dipanggil dengan segala cara untuk mengetahui nilai apa yang dibutuhkan peta. Lebih buruk lagi, sangat menggoda untuk menggunakan kembali peta yang dilewati ke fungsi saat ini untuk meneruskan data ke fungsi berikutnya. Ini berarti bahwa programmer yang mendukung aplikasi Anda perlu mengetahui semua fungsi yang dipanggil oleh fungsi saat ini untuk memahami apa fungsi saat ini. Itu persis kebalikan dari tujuan untuk menulis fungsi - mengabstraksi masalah sehingga Anda tidak perlu memikirkannya! Sekarang bayangkan 5 panggilan mendalam dan 5 panggilan lebar masing-masing. Banyak sekali yang harus diingat dan banyak kesalahan yang harus dilakukan.
"semuanya adalah peta" juga tampaknya mengarah pada penggunaan peta sebagai nilai balik. Aku telah melihatnya. Dan, sekali lagi, itu menyakitkan. Fungsi-fungsi yang dipanggil harus tidak pernah saling menimpa nilai balik satu sama lain - kecuali jika Anda mengetahui fungsionalitas segalanya dan tahu bahwa nilai peta input X perlu diganti untuk panggilan fungsi berikutnya. Dan fungsi saat ini perlu memodifikasi peta untuk mengembalikan nilainya, yang terkadang harus menimpa nilai sebelumnya dan kadang-kadang tidak.
edit - contoh
Berikut adalah contoh di mana ini bermasalah. Ini adalah aplikasi web. Input pengguna diterima dari lapisan UI dan ditempatkan di peta. Kemudian fungsi dipanggil untuk memproses permintaan. Set fungsi pertama akan memeriksa input yang salah. Jika ada kesalahan, pesan kesalahan akan diletakkan di peta. Fungsi panggilan akan memeriksa peta untuk entri ini dan menulis nilai dalam ui jika ada.
Set fungsi berikutnya akan memulai logika bisnis. Setiap fungsi akan mengambil peta, menghapus beberapa data, memodifikasi beberapa data, beroperasi pada data dalam peta dan meletakkan hasilnya di peta, dll. Fungsi selanjutnya akan mengharapkan hasil dari fungsi sebelumnya di peta. Untuk memperbaiki bug di fungsi berikutnya, Anda harus menyelidiki semua fungsi sebelumnya serta pemanggil untuk menentukan di mana saja nilai yang diharapkan mungkin telah ditetapkan.
Fungsi selanjutnya akan menarik data dari database. Atau, lebih tepatnya, mereka akan meneruskan peta ke lapisan akses data. DAL akan memeriksa apakah peta berisi nilai-nilai tertentu untuk mengontrol bagaimana kueri dieksekusi. Jika 'justcount' adalah kunci, maka kueri akan menjadi 'hitung pilih foo dari bilah'. Salah satu fungsi yang sebelumnya disebut mungkin memiliki fungsi yang menambahkan 'justcount' ke peta. Hasil kueri akan ditambahkan ke peta yang sama.
Hasilnya akan menggelembung ke penelepon (logika bisnis) yang akan memeriksa peta untuk apa yang harus dilakukan. Beberapa di antaranya akan berasal dari hal-hal yang ditambahkan ke peta oleh logika bisnis awal. Beberapa akan datang dari data dari database. Satu-satunya cara untuk mengetahui dari mana asalnya adalah menemukan kode yang menambahkannya. Dan lokasi lain yang juga bisa menambahkannya.
Kode ini secara efektif merupakan kekacauan monolitik, yang harus Anda pahami secara keseluruhan untuk mengetahui dari mana satu entri di peta berasal.
sumber
gameState
tanpa mengetahui apa-apa tentang apa yang terjadi sebelum atau sesudahnya. Ini hanya bereaksi terhadap data yang diberikan. Bagaimana Anda masuk ke situasi di mana fungsi-fungsi akan saling menginjak-injak kaki? Bisakah Anda memberi contoh?Secara pribadi, saya tidak akan merekomendasikan pola itu dalam paradigma yang manapun. Ini membuatnya lebih mudah untuk menulis pada awalnya dengan mengorbankan membuatnya lebih sulit untuk dipikirkan nanti.
Misalnya, coba jawab pertanyaan berikut tentang masing-masing fungsi subproses:
state
dibutuhkan?Dengan pola ini, Anda tidak dapat menjawab pertanyaan-pertanyaan itu tanpa membaca seluruh fungsi.
Dalam bahasa berorientasi objek, polanya bahkan lebih tidak masuk akal, karena melacak keadaan adalah apa yang dilakukan objek.
sumber
Apa yang tampaknya Anda lakukan adalah, secara efektif, monad negara manual; apa yang akan saya lakukan adalah membangun kombinator mengikat (disederhanakan) dan menyatakan kembali koneksi antara langkah logis Anda menggunakan itu:
Anda bahkan dapat menggunakan
stateBind
untuk membangun berbagai sub proses dari sub proses, dan melanjutkan ke bawah pohon kombinator yang mengikat untuk menyusun komputasi Anda dengan tepat.Untuk penjelasan tentang monad lengkap dan tidak disederhanakan, dan pengantar yang sangat baik untuk monad secara umum dalam JavaScript, lihat posting blog ini .
sumber
stateBind
kombinasi yang saya berikan adalah contoh yang sangat sederhana.Jadi, tampaknya ada banyak diskusi antara efektivitas pendekatan ini di Clojure. Saya pikir mungkin berguna untuk melihat filosofi Rich Hickey tentang mengapa ia menciptakan Clojure untuk mendukung abstraksi data dengan cara ini :
Fogus mengulangi poin-poin ini dalam bukunya Functional Javascript :
sumber
Pengacara Setan
Saya pikir pertanyaan ini layak untuk advokat iblis (tapi tentu saja saya bias). Saya pikir @KarlBielefeldt membuat poin yang sangat bagus dan saya ingin mengatasinya. Pertama saya ingin mengatakan bahwa poinnya bagus.
Karena dia menyebutkan ini bukan pola yang baik bahkan dalam pemrograman fungsional, saya akan mempertimbangkan JavaScript dan / atau Clojure di balasan saya. Satu kesamaan yang sangat penting antara kedua bahasa ini adalah mereka diketik secara dinamis. Saya akan lebih setuju dengan poinnya jika saya mengimplementasikan ini dalam bahasa yang diketik secara statis seperti Java atau Haskell. Tapi, saya akan mempertimbangkan alternatif dari pola "Semuanya adalah Peta" untuk menjadi desain OOP tradisional dalam JavaScript dan bukan dalam bahasa yang diketik secara statis (saya harap saya tidak menyiapkan argumen strawman dengan melakukan ini, tolong beritahu saya).
Dalam bahasa yang diketik secara dinamis, bagaimana Anda biasanya menjawab pertanyaan-pertanyaan ini? Parameter pertama suatu fungsi mungkin dinamai
foo
, tetapi apakah itu? Array? Sebuah Objek? Objek array objek? Bagaimana Anda mengetahuinya? Satu-satunya cara saya tahu adalah untukSaya tidak berpikir pola "Semuanya adalah Peta" membuat perbedaan di sini. Ini masih satu-satunya cara saya tahu untuk menjawab pertanyaan-pertanyaan ini.
Juga perlu diingat bahwa dalam JavaScript dan sebagian besar bahasa pemrograman imperatif, siapa pun
function
dapat meminta, memodifikasi, dan mengabaikan status apa pun yang dapat diakses dan tanda tangan tidak membuat perbedaan: Fungsi / metode dapat melakukan sesuatu dengan keadaan global atau dengan singleton. Tanda tangan sering berbohong.Saya tidak mencoba membuat dikotomi palsu antara "Semuanya adalah Peta" dan kode OO yang dirancang dengan buruk . Saya hanya mencoba menunjukkan bahwa memiliki tanda tangan yang menerima parameter berbutir lebih sedikit / lebih halus / kasar tidak menjamin Anda tahu cara mengisolasi, mengatur dan memanggil fungsi.
Tetapi, jika Anda mengizinkan saya untuk menggunakan dikotomi palsu itu: Dibandingkan dengan menulis JavaScript dengan cara OOP tradisional, "Semuanya adalah Peta" tampaknya lebih baik. Dengan cara OOP tradisional, fungsi tersebut mungkin memerlukan, mengubah atau mengabaikan keadaan yang Anda lewati atau menyatakan bahwa Anda tidak lulus. Dengan pola "Semuanya adalah Peta" ini, Anda hanya memerlukan, mengubah, atau mengabaikan keadaan yang Anda lewati di.
Dalam kode saya, ya. Lihat komentar kedua saya untuk jawaban @ Evicatos. Mungkin ini hanya karena saya membuat game, saya tidak bisa mengatakannya. Dalam gim yang memperbarui 60x per detik, tidak masalah apakah
dead guys drop loot
itugood guys pick up loot
atau sebaliknya. Setiap fungsi masih melakukan apa yang seharusnya dilakukan terlepas dari urutan apa yang mereka jalankan. Data yang sama hanya akan dimasukkan ke mereka padaupdate
panggilan yang berbeda jika Anda menukar pesanan. Jika Anda memilikigood guys pick up loot
kemudiandead guys drop loot
, orang-orang baik akan mengambil jarahan di depanupdate
dan itu bukan masalah besar. Manusia tidak akan bisa melihat perbedaannya.Setidaknya ini adalah pengalaman umum saya. Saya merasa sangat rentan mengakui hal ini di depan umum. Mungkin menganggap ini baik-baik saja adalah hal yang sangat , sangat buruk untuk dilakukan. Beri tahu saya jika saya melakukan kesalahan besar di sini. Tapi, jika saya memiliki, itu sangat mudah untuk mengatur ulang fungsi sehingga order
dead guys drop loot
kemudiangood guys pick up loot
lagi. Ini akan memakan waktu lebih sedikit daripada waktu yang dibutuhkan untuk menulis paragraf ini: PMungkin Anda berpikir "orang mati harus membuang barang rampasan terlebih dahulu. Akan lebih baik jika kode Anda menerapkan perintah itu". Tapi, mengapa musuh harus menjatuhkan jarahan sebelum kamu bisa mengambil jarahan? Bagi saya itu tidak masuk akal. Mungkin jarahan dijatuhkan 100 yang
updates
lalu. Tidak perlu memeriksa apakah orang jahat yang sewenang-wenang harus mengambil barang jarahan yang sudah ada di tanah. Itu sebabnya saya pikir urutan operasi ini sepenuhnya sewenang-wenang.Wajar untuk menulis langkah-langkah yang dipisahkan dengan pola ini, tetapi sulit untuk melihat langkah-langkah Anda dalam OOP tradisional. Jika saya menulis OOP tradisional, cara berpikir yang alami dan naif adalah membuat
dead guys drop loot
pengembalianLoot
objek yang harus saya lewatigood guys pick up loot
. Saya tidak akan dapat menyusun ulang operasi tersebut sejak yang pertama mengembalikan input yang kedua.Objek memiliki status dan idiom untuk mengubah keadaan dan membuat riwayatnya hilang begitu saja ... kecuali jika Anda menulis kode secara manual untuk melacaknya. Dengan cara apa melacak status "apa yang mereka lakukan"?
Benar, seperti yang saya katakan, "Jarang ada fungsi saya yang murni". Mereka selalu hanya beroperasi pada parameter mereka, tetapi mereka mengubah parameter mereka. Ini adalah kompromi yang saya rasa harus saya buat ketika menerapkan pola ini ke JavaScript.
sumber
parent
? Apakahrepetitions
dan susunan angka atau string atau tidak penting? Atau mungkin pengulangan hanyalah angka untuk mewakili jumlah represi yang saya inginkan? Ada banyak apis di luar sana yang hanya mengambil objek opsi . Dunia adalah tempat yang lebih baik jika Anda memberi nama dengan benar, tetapi itu tidak menjamin Anda akan tahu cara menggunakan api, tidak ada pertanyaan yang diajukan.Saya telah menemukan bahwa kode saya cenderung berakhir terstruktur seperti:
Saya tidak memulai untuk membuat perbedaan ini, tetapi sering kali itu berakhir pada kode saya. Saya tidak berpikir menggunakan satu gaya harus meniadakan yang lain.
Fungsi murni mudah untuk unit test. Yang lebih besar dengan peta lebih banyak masuk ke area uji "integrasi" karena cenderung melibatkan lebih banyak bagian yang bergerak.
Dalam javascript, satu hal yang banyak membantu adalah menggunakan sesuatu seperti pustaka Meteor's Match untuk melakukan validasi parameter. Itu membuatnya sangat jelas apa fungsi yang diharapkan dan dapat menangani peta dengan cukup bersih.
Sebagai contoh,
Lihat http://docs.meteor.com/#match untuk informasi lebih lanjut.
:: PEMBARUAN ::
Rekaman video Stuart Sierra tentang Clojure / "Clojure in the Large" Barat juga menyentuh masalah ini. Seperti OP, ia mengontrol efek samping sebagai bagian dari peta sehingga pengujian menjadi lebih mudah. Dia juga memiliki posting blog yang menguraikan alur kerja Clojure saat ini yang tampaknya relevan.
sumber
Argumen utama yang dapat saya pikirkan terhadap praktik ini adalah bahwa sangat sulit untuk mengatakan data apa yang sebenarnya dibutuhkan suatu fungsi.
Apa artinya itu adalah bahwa programmer masa depan dalam basis kode harus tahu bagaimana fungsi yang dipanggil bekerja secara internal - dan setiap fungsi panggilan bersarang - untuk menyebutnya.
Semakin saya memikirkannya, semakin banyak objek gameState Anda yang berbau global. Jika seperti itulah cara itu digunakan, mengapa menyebarkannya?
sumber
gameState = f(gameState)
ke sayaf()
, itu jauh lebih sulit untuk diujif
.f()
dapat mengembalikan hal yang berbeda setiap kali saya menyebutnya. Tapi mudah untukf(gameState)
mengembalikan hal yang sama setiap kali diberi input yang sama.Ada nama yang lebih pas untuk apa yang Anda lakukan daripada bola besar lumpur . Apa yang Anda lakukan disebut pola objek Dewa . Itu tidak terlihat seperti itu pada pandangan pertama, tetapi dalam Javascript ada sedikit perbedaan antara
dan
Apakah itu ide yang baik atau tidak mungkin tergantung pada keadaan. Tapi ini tentu sejalan dengan cara Clojure. Salah satu tujuan Clojure adalah untuk menghapus apa yang disebut Rich Hickey "kompleksitas insidental". Berbagai objek yang berkomunikasi tentu lebih kompleks daripada objek tunggal. Jika Anda membagi fungsionalitas menjadi beberapa objek, Anda tiba-tiba harus khawatir tentang komunikasi dan koordinasi dan membagi tanggung jawab. Itu adalah komplikasi yang hanya terkait dengan tujuan awal Anda menulis sebuah program. Anda akan melihat perkataan Rich Hickey menjadi mudah . Saya pikir ini adalah ide yang sangat bagus.
sumber
Saya baru saja menghadapi topik ini sebelumnya hari ini ketika bermain dengan proyek baru. Saya bekerja di Clojure untuk membuat game Poker. Saya mewakili nilai nominal dan setelan sebagai kata kunci, dan memutuskan untuk mewakili kartu sebagai peta
Saya bisa saja membuat mereka daftar atau vektor dari dua elemen kata kunci. Saya tidak tahu apakah itu membuat perbedaan memori / kinerja jadi saya hanya akan menggunakan peta untuk saat ini.
Jika saya berubah pikiran kemudian, saya memutuskan bahwa sebagian besar program saya harus melalui "antarmuka" untuk mengakses potongan-potongan kartu, sehingga detail implementasi dikontrol dan disembunyikan. Saya punya fungsi
yang digunakan sisa program. Kartu diteruskan ke fungsi sebagai peta, tetapi fungsi menggunakan antarmuka yang disepakati untuk mengakses peta dan dengan demikian seharusnya tidak dapat mengacaukan.
Dalam program saya, kartu mungkin hanya akan menjadi peta bernilai 2. Dalam pertanyaan, seluruh status game dilewatkan sebagai peta. Status permainan akan jauh lebih rumit daripada kartu tunggal, tapi saya rasa tidak ada kesalahan untuk menggunakan peta. Dalam bahasa object-imperative saya bisa saja memiliki satu objek GameState besar dan memanggil metodenya, dan memiliki masalah yang sama:
Sekarang berorientasi objek. Apakah ada yang salah dengan itu? Saya tidak berpikir begitu, Anda hanya mendelegasikan pekerjaan ke fungsi yang tahu bagaimana menangani objek Negara. Dan apakah Anda bekerja dengan peta atau objek, Anda harus waspada kapan harus membaginya menjadi potongan-potongan kecil. Jadi saya katakan menggunakan peta tidak masalah, selama Anda menggunakan perawatan yang sama yang akan Anda gunakan dengan objek.
sumber
Dari apa (kecil) yang saya lihat, menggunakan peta atau struktur bersarang lainnya untuk membuat satu objek keadaan global tidak berubah seperti ini cukup umum dalam bahasa fungsional, setidaknya yang murni, terutama ketika menggunakan State Monad sebagai @ Ptharien'sFlame mentioend .
Dua penghalang untuk menggunakan ini secara efektif yang telah saya lihat / baca (dan jawaban lain yang disebutkan di sini) adalah:
Ada beberapa teknik / pola umum yang berbeda yang dapat membantu meringankan masalah ini:
Yang pertama adalah Ritsleting : ini memungkinkan seseorang melintasi dan bermutasi jauh di dalam hierarki bersarang abadi.
Lain adalah Lensa : ini memungkinkan Anda fokus ke struktur ke lokasi tertentu dan membaca / mengubah nilai di sana. Anda dapat menggabungkan lensa yang berbeda bersama-sama untuk fokus pada hal-hal yang berbeda, seperti rantai properti yang dapat disesuaikan di OOP (di mana Anda dapat mengganti variabel untuk nama properti yang sebenarnya!)
Prismatik baru-baru ini membuat posting blog tentang penggunaan teknik semacam ini, antara lain, dalam JavaScript / ClojureScript, yang harus Anda periksa. Mereka menggunakan kursor (yang mereka bandingkan dengan ritsleting) ke kondisi jendela untuk fungsi:
IIRC, mereka juga menyentuh ketidakmampuan dalam JavaScript dalam posting itu.
sumber
assoc-in
et al). Kira saya hanya punya Haskell di otak. Saya ingin tahu apakah ada yang melakukan port JavaScript? Orang mungkin tidak mengungkitnya karena (seperti saya) mereka tidak menonton pembicaraan :)Apakah ini ide yang bagus atau tidak, akan sangat tergantung pada apa yang Anda lakukan dengan keadaan di dalam subproses tersebut. Jika saya memahami contoh Clojure dengan benar, kamus-kamus negara bagian yang dikembalikan bukanlah kamus-kamus negara bagian yang sama yang dilewatkan. Mereka adalah salinan, mungkin dengan tambahan dan modifikasi, yang (saya asumsikan) Clojure mampu membuat secara efisien karena sifat fungsional bahasa tergantung padanya. Kamus kondisi asli untuk setiap fungsi tidak dimodifikasi dengan cara apa pun.
Jika saya memahami dengan benar, Anda akan memodifikasi obyek negara Anda masuk ke dalam fungsi javascript Anda daripada kembali salinan, yang berarti Anda melakukan sesuatu yang sangat, sangat, berbeda dari apa yang kode Clojure lakukan. Seperti yang ditunjukkan Mike Partridge, ini pada dasarnya hanya global yang secara eksplisit Anda berikan dan kembali dari fungsi tanpa alasan yang jelas. Pada titik ini saya pikir itu hanya membuat Anda berpikir Anda melakukan sesuatu yang sebenarnya tidak Anda lakukan.
Jika Anda benar-benar secara eksplisit membuat salinan negara, memodifikasinya, dan kemudian mengembalikan salinan yang dimodifikasi itu, kemudian lanjutkan. Saya tidak yakin itu selalu cara terbaik untuk mencapai apa yang Anda coba lakukan dalam Javascript, tetapi mungkin "dekat" dengan apa yang dilakukan contoh Clojure.
sumber
update()
fungsi saya . Saya memindahkan satu ke atas dan satu ke bawah. Semua tes saya masih berlalu dan ketika saya memainkan permainan saya tidak melihat efek buruk. Saya merasa fungsi saya sama komposisi seperti contoh Clojure. Kami berdua membuang data lama kami setelah setiap langkah.(-> 10 (- 5) (/ 2))
mengembalikan 2.5.(-> 10 (/ 2) (- 5))
mengembalikan 0.Jika Anda memiliki objek keadaan global, kadang-kadang disebut "objek dewa", yang diteruskan ke setiap proses, Anda akhirnya mengacaukan sejumlah faktor, yang semuanya meningkatkan kopling, sekaligus mengurangi kohesi. Semua faktor ini berdampak negatif terhadap pemeliharaan jangka panjang.
Tramp Coupling Ini muncul dari melewatkan data melalui berbagai metode yang tidak perlu untuk hampir semua data, untuk mendapatkannya ke tempat yang benar-benar bisa menanganinya. Kopling semacam ini mirip dengan menggunakan data global, tetapi bisa lebih berisi. Tramp coupling adalah kebalikan dari "need to know", yang digunakan untuk melokalisasi efek dan untuk menahan kerusakan yang bisa terjadi pada satu kode yang salah pada seluruh sistem.
Navigasi Data Setiap sub-proses dalam contoh Anda perlu tahu cara mendapatkan data yang dibutuhkan secara tepat, dan perlu memprosesnya dan mungkin membangun objek keadaan global baru. Itu adalah konsekuensi logis dari tramp coupling; seluruh konteks suatu datum diperlukan untuk beroperasi pada datum. Sekali lagi, pengetahuan non-lokal adalah hal yang buruk.
Jika Anda memasukkan "ritsleting", "lensa", atau "kursor", seperti dijelaskan dalam posting oleh @paul, itu adalah satu hal. Anda akan mengandung akses, dan memungkinkan ritsleting, dll, untuk mengontrol membaca dan menulis data.
Pelanggaran Tanggung Jawab Tunggal Mengklaim masing-masing "subproses-satu", "subprocess-dua" dan "subprocess-tiga" hanya memiliki satu tanggung jawab tunggal, yaitu untuk menghasilkan objek negara global baru dengan nilai-nilai yang tepat di dalamnya, adalah reduksionisme mengerikan. Ini semua pada akhirnya, bukan?
Maksud saya di sini adalah memiliki semua komponen utama gim Anda memiliki semua tanggung jawab yang sama dengan gim Anda mengalahkan tujuan pendelegasian dan anjak piutang.
Dampak Sistem
Dampak utama dari desain Anda adalah rawatan yang rendah. Fakta bahwa Anda dapat menyimpan seluruh permainan di kepala Anda mengatakan bahwa Anda sangat mungkin seorang programmer yang hebat. Ada banyak hal yang telah saya rancang yang dapat saya ingat di seluruh proyek. Itu bukan poin dari rekayasa sistem. Intinya adalah membuat sistem yang dapat bekerja untuk sesuatu yang lebih besar daripada yang dapat disimpan oleh satu orang di kepalanya secara bersamaan .
Menambahkan programmer lain, atau dua, atau delapan, akan menyebabkan sistem Anda berantakan segera.
Akhirnya
sumber