Saat pemrograman dalam gaya Fungsional, apakah Anda memiliki satu status aplikasi yang Anda anyam melalui logika aplikasi?

12

Bagaimana cara membuat sistem yang memiliki semua hal berikut :

  1. Menggunakan fungsi murni dengan objek yang tidak berubah.
  2. Hanya meneruskan ke data fungsi yang dibutuhkan fungsi, tidak lebih (yaitu tidak ada objek status aplikasi besar)
  3. Hindari terlalu banyak argumen pada fungsi.
  4. 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.

Daisha Lynn
sumber
1
Saya bukan ahli dalam desain dan khususnya fungsional, tetapi karena permainan Anda secara alami memiliki keadaan yang berkembang, apakah Anda yakin bahwa pemrograman fungsional adalah paradigma yang sesuai dengan semua lapisan aplikasi Anda?
Walfrat
Walfrat, saya pikir jika Anda berbicara dengan para ahli pemrograman fungsional, Anda mungkin akan menemukan bahwa mereka akan mengatakan bahwa paradigma pemrograman fungsional memiliki solusi untuk mengelola keadaan yang berkembang.
Daisha Lynn
Pertanyaan Anda tampak lebih luas bagi saya yang hanya menyatakan. Jika ini hanya tentang mengelola status di sini adalah permulaan: lihat jawaban dan tautan di dalam stackoverflow.com/questions/1020653/…
Walfrat
2
@DaishaLynn Saya tidak berpikir Anda harus menghapus pertanyaan. Sudah dicabut dan tidak ada yang mencoba untuk menutupnya, jadi saya tidak berpikir itu di luar jangkauan untuk situs ini. Kurangnya jawaban sejauh ini mungkin hanya karena membutuhkan beberapa keahlian yang relatif niche. Tetapi itu tidak berarti itu tidak akan ditemukan dan dijawab pada akhirnya.
Ben Aaronson
2
Mengelola keadaan yang bisa berubah-ubah dalam program fungsional murni yang kompleks tanpa bantuan bahasa yang substansial merupakan hal yang sangat menyakitkan. Dalam Haskell itu dikelola karena monad, sintaks singkat, inferensi tipe yang sangat baik tetapi masih bisa sangat menjengkelkan. Di C # saya pikir Anda akan memiliki lebih banyak masalah.
Pasang kembali Monica

Jawaban:

2

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:

  • Membagi secara GameStatehierarkis, sehingga Anda mendapatkan 3-5 bagian yang lebih kecil, bukan 15.
  • Biarkan mengimplementasikan antarmuka, sehingga metode Anda hanya melihat bagian yang diperlukan. Jangan pernah melemparkannya kembali karena Anda akan berbohong pada diri sendiri tentang tipe yang sebenarnya.
  • Biarkan juga bagian-bagian mengimplementasikan antarmuka, sehingga Anda mendapatkan kontrol yang baik atas apa yang Anda lewati.
  • Gunakan objek parameter, tetapi lakukan dengan hemat dan cobalah mengubahnya menjadi objek nyata dengan perilaku mereka sendiri.
  • Kadang-kadang melewati sedikit lebih dari yang dibutuhkan lebih baik daripada daftar parameter yang panjang.

Jadi sekarang saya bertanya-tanya apakah masalah yang saya miliki adalah bahwa fungsi saya bersarang terlalu dalam.

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?

maaartinus
sumber
Seseorang mengatakan kepada saya untuk mengubah desain saya sehingga fungsi yang hanya membutuhkan satu parameter sehingga saya dapat menggunakan currying. Saya mencoba fungsi satu itu, jadi alih-alih memanggil DeleteEntity (a, b, c), sekarang saya memanggil DeleteEntity (a) (b) (c). Jadi itu lucu, dan itu seharusnya membuatnya menjadi lebih komposif, tapi saya belum mendapatkannya.
Daisha Lynn
@DaishaLynn Saya menggunakan Java dan tidak ada gula sintaksis manis untuk kari, jadi (bagi saya) itu tidak pantas untuk dicoba. Saya agak skeptis tentang kemungkinan penggunaan fungsi tingkat tinggi dalam kasus kami, tetapi beri tahu saya jika itu berfungsi untuk Anda.
maaartinus
2

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

public class MyState
{
    public int MyInt {get; set; }
    public string MyString {get; set; }
}

Anda bisa mendefinisikan antarmuka IHasMyIntdan IHasMyStringdengan metode GetMyIntdan GetMyStringmasing - masing. Kelas negara kemudian terlihat seperti:

public class MyState : IHasMyInt, IHasMyString
{
    public int MyInt {get; set; }
    public string MyString {get; set; }
    public double MyDouble {get; set; }

    public int GetMyInt () 
    {
        return MyInt;
    }

    public string GetMyString ()
    {
        return MyString;
    }

    public double GetMyDouble ()
    {
        return MyDouble;
    }
}

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.

public static T DoSomething<T>(T state) where T : IHasMyString, IHasMyInt
{
    var s = state.GetMyString();
    var i = state.GetMyInt();
    return state;
}
DylanSp
sumber
Itu menarik. Jadi saat ini, di mana saya meneruskan panggilan fungsi dan lulus 10 parameter dengan nilai, saya melewati "gameSt'ate" 10 kali, tetapi ke 10 jenis parameter yang berbeda, seperti "IHasGameScore", "IHasGameBoard", dll. Saya berharap ada adalah cara untuk melewatkan parameter tunggal yang fungsi dapat menunjukkan harus mengimplementasikan semua antarmuka dalam satu jenis. Saya ingin tahu apakah saya dapat melakukannya dengan "batasan generik" .. Biarkan saya mencobanya.
Daisha Lynn
1
Itu berhasil. Ini dia berfungsi: dotnetfiddle.net/cfmDbs .
Daisha Lynn
1

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.

Daniel T.
sumber
Saya tidak tahu Elm, tapi saya percaya itu mirip dengan Redux. Di Redux, bukankah semua reduksi dipanggil untuk setiap perubahan status? Kedengarannya sangat tidak efisien.
Daisha Lynn
Ketika datang ke optimasi tingkat rendah, jangan menganggap, mengukur. Dalam praktiknya itu cukup cepat.
Daniel T.
Terima kasih Daniel, tetapi itu tidak akan berhasil untuk saya. Saya telah melakukan pengembangan yang cukup untuk mengetahui bahwa itu tidak membuat memberitahukan setiap komponen di UI setiap saat ada perubahan data, terlepas dari apakah kontrol peduli dengan kontrol.
Daisha Lynn
-2

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.

t3chb0t
sumber
3
Tidak senang dengan jawaban ini, maupun nada. Saya tidak menyalahgunakan apa pun. Saya mendorong batas C # untuk menggunakannya sebagai bahasa yang lebih fungsional. Ini bukan hal yang biasa dilakukan. Anda tampaknya secara filosofis menentangnya, yang baik-baik saja, tetapi dalam hal ini, jangan melihat pertanyaan ini. Komentar Anda tidak berguna bagi siapa pun. Berpindah.
Daisha Lynn
@DaishaLynn Anda salah, saya tidak menentang dengan cara apa pun dan sebenarnya saya sering menggunakannya ... tapi di mana itu wajar dan mungkin dan tidak mencoba mengubah bahasa OO menjadi bahasa fungsional hanya karena itu hip untuk melakukannya. Anda tidak harus setuju dengan jawaban saya tetapi itu tidak mengubah fakta bahwa Anda tidak menggunakan alat Anda dengan benar.
t3chb0t
Saya tidak melakukannya karena pinggul untuk melakukannya. C # sendiri bergerak ke arah penggunaan gaya fungsional. Anders Hejlsberg sendiri telah mengindikasikan hal itu. Saya mengerti Anda hanya tertarik pada penggunaan arus utama bahasa, dan saya mengerti mengapa dan kapan itu tepat. Saya hanya tidak tahu mengapa seseorang seperti Anda bahkan ada di utas ini .. Bagaimana Anda benar-benar membantu?
Daisha Lynn
@DaishaLynn jika Anda tidak dapat mengatasi jawaban yang mengkritik pertanyaan atau pendekatan Anda, Anda mungkin tidak boleh mengajukan pertanyaan di sini atau lain kali Anda hanya perlu menambahkan penafian yang mengatakan bahwa Anda hanya tertarik pada jawaban yang mendukung ide Anda 100% karena Anda tidak ingin mendengar kebenaran tetapi mendapatkan opini yang mendukung.
t3chb0t
Harap sedikit lebih ramah satu sama lain. Adalah mungkin untuk memberikan kritik tanpa meremehkan bahasa. Mencoba memprogram C # dalam gaya fungsional tentu bukan "penyalahgunaan" atau kasus tepi. Ini adalah teknik umum yang digunakan oleh banyak pengembang C # untuk belajar dari bahasa lain.
zumalifeguard