Bagaimana cara membuat sistem yang memiliki semua hal berikut :
- Menggunakan fungsi murni dengan objek yang tidak berubah.
- Hanya meneruskan ke data fungsi yang dibutuhkan fungsi, tidak lebih (yaitu tidak ada objek status aplikasi besar)
- Hindari terlalu banyak argumen pada fungsi.
- Hindari harus membuat objek baru hanya untuk tujuan mengemas dan membongkar parameter ke fungsi, hanya untuk menghindari terlalu banyak parameter yang diteruskan ke fungsi. Jika saya akan mengemas beberapa item ke fungsi sebagai objek tunggal, saya ingin objek itu menjadi pemilik data itu, bukan sesuatu yang dibangun sementara
Sepertinya saya bahwa Negara monad melanggar aturan # 2, meskipun itu tidak jelas karena itu dijalin melalui monad.
Saya punya perasaan saya perlu menggunakan Lensa entah bagaimana, tetapi sangat sedikit yang ditulis tentang itu untuk bahasa non-Fungsional.
Latar Belakang
Sebagai latihan, saya mengubah salah satu aplikasi saya yang ada dari gaya berorientasi objek ke gaya fungsional. Hal pertama yang saya coba lakukan adalah membuat sebanyak mungkin inti-dalam dari aplikasi.
Satu hal yang saya dengar adalah bagaimana mengelola "Negara" dalam bahasa yang benar-benar fungsional, dan ini yang saya yakini dilakukan oleh State Monads, adalah bahwa secara logis, Anda menyebut fungsi murni, "melintas di negara bagian dunia apa adanya ", maka ketika fungsi kembali, ia mengembalikan kepada Anda keadaan dunia sebagaimana telah berubah.
Sebagai ilustrasi, cara Anda dapat melakukan "hello world" dengan cara yang benar-benar fungsional agak mirip, Anda meneruskan program Anda yang menyatakan layar, dan menerima kembali keadaan layar dengan "hello world" yang tercetak di atasnya. Jadi secara teknis, Anda melakukan panggilan ke fungsi murni, dan tidak ada efek samping.
Berdasarkan hal itu, saya memeriksa aplikasi saya, dan: 1. Pertama, letakkan semua status aplikasi saya menjadi satu objek global (GameState) 2. Kedua, saya membuat GameState tidak dapat diubah. Anda tidak bisa mengubahnya. Jika Anda butuh perubahan, Anda harus membuat yang baru. Saya melakukan ini dengan menambahkan copy-constructor, yang secara opsional mengambil satu atau lebih bidang yang berubah. 3. Untuk setiap aplikasi, saya mengirimkan GameState sebagai parameter. Dalam fungsi, setelah melakukan apa yang akan dilakukan, itu menciptakan GameState baru dan mengembalikannya.
Bagaimana saya memiliki inti fungsional murni, dan loop di luar yang memberi makan GameState itu ke loop alur kerja utama aplikasi.
Pertanyaan saya:
Sekarang, masalah saya adalah, GameState memiliki sekitar 15 objek abadi yang berbeda. Banyak fungsi di level terendah hanya beroperasi pada beberapa objek tersebut, seperti menjaga skor. Jadi, katakanlah saya memiliki fungsi yang menghitung skor. Hari ini, GameState diteruskan ke fungsi ini, yang mengubah skor dengan membuat GameState baru dengan skor baru.
Sesuatu tentang itu sepertinya salah. Fungsi tidak membutuhkan keseluruhan GameState. Itu hanya membutuhkan objek Skor. Jadi saya memperbaruinya untuk memberikan Skor, dan mengembalikan Skor saja.
Itu sepertinya masuk akal, jadi saya melangkah lebih jauh dengan fungsi-fungsi lain. Beberapa fungsi akan mengharuskan saya untuk lulus dalam 2, 3 atau 4 parameter dari GameState, tetapi ketika saya menggunakan pola tersebut sepanjang inti luar aplikasi, saya memberikan lebih banyak status aplikasi. Seperti, di bagian atas loop alur kerja, saya akan memanggil metode, yang akan memanggil metode yang akan memanggil metode, dll., Semua jalan ke tempat skor dihitung. Itu berarti skor saat ini diteruskan melalui semua lapisan itu hanya karena fungsi di bagian paling bawah akan menghitung skor.
Jadi sekarang saya memiliki fungsi dengan terkadang puluhan parameter. Saya bisa meletakkan parameter-parameter itu ke dalam objek untuk menurunkan jumlah parameter, tapi kemudian saya ingin kelas itu menjadi lokasi utama dari status aplikasi negara, daripada objek yang dibangun pada saat panggilan hanya untuk menghindari lewat dalam beberapa parameter, dan kemudian ekstrak mereka.
Jadi sekarang saya bertanya-tanya apakah masalah yang saya miliki adalah bahwa fungsi saya bersarang terlalu dalam. Ini adalah hasil dari ingin memiliki fungsi kecil, jadi saya refactor ketika suatu fungsi menjadi besar, dan membaginya menjadi beberapa fungsi yang lebih kecil. Tetapi melakukan hal itu menghasilkan hierarki yang lebih dalam, dan apa pun yang dilewatkan ke fungsi dalam perlu dialihkan ke fungsi luar bahkan jika fungsi luar tidak beroperasi pada objek-objek itu secara langsung.
Sepertinya hanya lewat di GameState sepanjang jalan menghindari masalah ini. Tetapi saya kembali ke masalah semula dengan memberikan lebih banyak informasi ke suatu fungsi daripada fungsi yang dibutuhkan.
sumber
Jawaban:
Saya tidak yakin apakah ada solusi yang baik. Ini mungkin atau bukan jawaban, tapi terlalu panjang untuk dikomentari. Saya melakukan hal serupa dan trik-trik berikut telah membantu:
GameState
hierarkis, sehingga Anda mendapatkan 3-5 bagian yang lebih kecil, bukan 15.Saya kira tidak. Refactoring ke fungsi-fungsi kecil benar, tetapi mungkin Anda bisa mengelompokkannya kembali dengan lebih baik. Kadang-kadang, itu tidak mungkin, kadang-kadang hanya perlu melihat kedua (atau ketiga) masalah.
Bandingkan desain Anda dengan yang bisa berubah-ubah. Apakah ada hal-hal yang menjadi lebih buruk dengan penulisan ulang? Jika demikian, tidak bisakah Anda membuatnya lebih baik dengan cara yang sama seperti yang Anda lakukan pada awalnya?
sumber
Saya tidak bisa berbicara dengan C #, tetapi di Haskell, Anda akan melewati seluruh negara bagian. Anda bisa melakukan ini secara eksplisit atau dengan monad negara. Satu hal yang dapat Anda lakukan untuk mengatasi masalah fungsi yang menerima informasi lebih banyak daripada yang mereka butuhkan adalah dengan menggunakan kacamata ketik. (Jika Anda tidak terbiasa, Haskell typeclasses sedikit seperti antarmuka C #.) Untuk setiap elemen E dari negara, Anda dapat mendefinisikan sebuah typeclass HasE yang memerlukan fungsi getE yang mengembalikan nilai E. Monad negara kemudian dapat menjadi membuat contoh dari semua typeclasses ini. Kemudian dalam fungsi Anda yang sebenarnya, alih-alih secara eksplisit membutuhkan monad Negara Anda, Anda memerlukan monad apa pun yang termasuk dalam typeclasses Has untuk elemen yang Anda perlukan; yang membatasi apa yang bisa dilakukan fungsi dengan monad yang digunakannya. Untuk info lebih lanjut tentang pendekatan ini, lihat Michael Snoymanposting pada pola desain ReaderT .
Anda mungkin bisa meniru sesuatu seperti ini di C #, tergantung pada bagaimana Anda mendefinisikan negara yang sedang diedarkan. Jika Anda memiliki sesuatu seperti
Anda bisa mendefinisikan antarmuka
IHasMyInt
danIHasMyString
dengan metodeGetMyInt
danGetMyString
masing - masing. Kelas negara kemudian terlihat seperti:maka metode Anda mungkin memerlukan IHasMyInt, IHasMyString, atau seluruh MyState yang sesuai.
Anda kemudian dapat menggunakan batasan di mana pada definisi fungsi sehingga Anda dapat melewati objek negara, tetapi hanya bisa mendapatkan ke string dan int, bukan ganda.
sumber
Saya pikir Anda sebaiknya mempelajari tentang Redux atau Elm dan bagaimana mereka menangani pertanyaan ini.
Pada dasarnya, Anda memiliki satu fungsi murni yang mengambil seluruh status dan tindakan yang dilakukan pengguna dan mengembalikan status baru.
Fungsi itu kemudian memanggil fungsi murni lainnya, yang masing-masing menangani bagian tertentu dari negara. Bergantung pada tindakan, banyak fungsi-fungsi ini dapat melakukan apa-apa selain mengembalikan keadaan semula tidak berubah.
Untuk mempelajari lebih lanjut, Google Arsitektur Elm atau Redux.js.org.
sumber
Saya pikir apa yang Anda coba lakukan adalah menggunakan bahasa berorientasi objek dengan cara yang seharusnya tidak digunakan, seolah-olah itu adalah bahasa fungsional murni. Ini tidak seperti bahasa OO yang semuanya jahat. Ada beberapa keuntungan dari kedua pendekatan tersebut sehingga kami sekarang dapat memadukan gaya OO dengan gaya fungsional dan memiliki kesempatan untuk membuat beberapa bagian kode berfungsi sementara yang lain tetap berorientasi objek sehingga kami dapat mengambil keuntungan dari semua usabilitas, warisan atau polimofisme. Untungnya kita tidak lagi terikat pada pendekatan mana pun hanya jadi mengapa Anda mencoba membatasi diri pada salah satu dari mereka?
Menjawab pertanyaan Anda: tidak, saya tidak menenun negara tertentu melalui logika aplikasi tetapi menggunakan apa yang sesuai untuk kasus penggunaan saat ini dan menerapkan teknik yang tersedia dengan cara yang paling tepat.
C # belum siap (untuk digunakan) berfungsi seperti yang Anda inginkan.
sumber