Pemrograman fungsional dan petualangan Teks

14

Ini sebagian besar pertanyaan teoretis tentang FP, tetapi saya akan mengambil petualangan teks (seperti Zork sekolah lama) untuk mengilustrasikan poin saya. Saya ingin tahu pendapat Anda tentang bagaimana Anda memodelkan simulasi stateful dengan FP.

Petualangan teks tampaknya benar-benar membutuhkan OOP. Sebagai contoh, semua "kamar" adalah contoh dari sebuah Roomkelas, Anda dapat memiliki Itemkelas dasar dan antarmuka seperti Item<Pickable>untuk hal-hal yang dapat Anda bawa dan sebagainya.

Pemodelan dunia dalam FP bekerja secara berbeda, terutama jika Anda ingin menegakkan kekekalan di dunia yang harus bermutasi saat permainan berlangsung (objek dipindahkan, musuh dikalahkan, skor bertambah, pemain mengubah lokasinya). Saya membayangkan satu objek besar Worldyang memiliki semuanya: kamar apa yang bisa Anda jelajahi, bagaimana mereka terhubung, apa yang dibawa pemain, tuas apa yang telah dipicu.

Saya pikir pendekatan murni pada dasarnya akan melewatkan objek besar ini ke fungsi apa pun dan mengembalikannya (mungkin dimodifikasi). Sebagai contoh, saya memiliki moveToRoomfungsi yang mendapat Worlddan mengembalikannya dengan World.player.locationdiubah ke ruang baru, World.rooms[new_room].visited = Truedan seterusnya.

Bahkan jika ini adalah cara yang lebih "benar", tampaknya ini menegakkan kemurnian demi hal itu. Bergantung pada bahasa pemrogramannya, melewatkan objek yang berpotensi sangat besar ini Worldbolak-balik mungkin mahal. Juga, setiap fungsi mungkin perlu memiliki akses ke Worldobjek apa pun . Sebagai contoh, sebuah ruangan mungkin dapat diakses atau tidak tergantung pada tuas yang dipicu di ruangan lain karena itu mungkin banjir, tetapi jika pemain membawa jaket pelampung, itu tetap bisa masuk. Monster mungkin agresif atau tidak, tergantung pada apakah pemain telah membunuh sepupunya di ruangan lain. Ini berarti bahwa roomCanBeEnteredfungsi perlu mengakses World.player.invetorydan World.rooms, describeMonsterperlu mengakses World.monstersdan sebagainya (pada dasarnya, Anda haruslulus seluruh beban sekitar). Bagi saya ini benar-benar memanggil untuk variabel global, bahkan jika ini semua kecuali gaya pemrograman yang baik terutama dalam FP.

Bagaimana Anda memecahkan masalah ini?

pistacchio
sumber
4
"Tergantung pada bahasa pemrogramannya, melewati objek dunia yang berpotensi sangat besar ini bolak-balik mungkin mahal." Mungkin akan dilewatkan dengan referensi. "Juga, setiap fungsi mungkin perlu memiliki akses ke objek Dunia apa pun." Saya merasa sulit untuk percaya bahwa setiap fungsi membutuhkan akses ke seluruh kondisi permainan.
Doval
2
Saya pikir penelitian Chris Marten akan menarik, ini bertujuan untuk menunjukkan bagaimana membuat fiksi interaktif dalam bahasa deklaratif bagus. github.com/chrisamaphone/interactive-lp
Daniel Gratzer
2
Anda mungkin ingin melihat blog ini menggambarkan pendekatan penulis untuk memprogram permainan seperti itu secara fungsional. Posting ini khususnya cukup tepat.
gallais
3
Saya harus bertanya-tanya apakah pertanyaan ini memengaruhi keputusan @ EricLippert nanti untuk menulis serangkaian artikel tentang penerapan (bagian) mesin Z di Ocaml ...?
Jules
1
@Jules A tautan ke awal seri itu, untuk mereka yang tertarik: ericlippert.com/2016/02/01/west-of-house
KChaloux

Jawaban:

4

Ingatlah bahwa bahasa fungsional menggunakan struktur data dan fungsi yang dipisahkan alih-alih objek. Misalnya, Anda akan memiliki satu set kamar dan daftar barang inventaris sebagai dunia sebagai gantinya.

Idealnya, Anda juga akan membatasi jumlah data yang Anda berikan pada fungsi hingga berapa banyak yang sebenarnya mereka butuhkan sebanyak mungkin alih-alih melewati seluruh dunia (katakanlah Anda mengekstrak satu kamar yang relevan dari dunia Anda; tentu saja dunia yang saling bergantung secara penuh bisa sulit untuk dilakukan. terpisah). Hasilnya akan dimasukkan kembali ke dalam struktur data dunia, menciptakan keadaan baru. Anda tidak dapat memodelkan status tanpa menggunakan status; seperti yang Anda katakan beberapa hal secara inheren membutuhkan mutasi.

Sebagian besar bahasa fungsional praktis menyediakan cara untuk mewujudkan mutasi baik secara langsung (misalnya, monad ST di Haskell atau transien di Clojure), atau mensimulasikannya secara efisien (sering kali dengan menggunakan kembali bagian struktur data yang tidak berubah (struktur data default Clojure adalah contoh yang baik di sini) ). Bagaimanapun kemurnian dipertahankan.

Karena jumlah status yang perlu dimutasi tampaknya terbatas, saya tidak akan terlalu khawatir tentang masalah kinerja dan mengikuti pendekatan fungsional yang naif (mungkin sudah dioptimalkan).

Pilihan berbeda yang saya lihat adalah mengembalikan hanya instruksi untuk mengubah beberapa bagian dunia dari fungsi individual Anda, dan kemudian memperbarui dunia Anda sesuai dengan ini. Serangkaian posting blog yang menjelaskan hal ini tersedia di http://prog21.dadgum.com/23.html .

Kedua jawaban ini lebih berurusan dengan bagaimana mengatur perubahan daripada tidak menyerahkan seluruh dunia Anda ke fungsi, karena yang saling bergantung sempurna tidak dapat dibagi berdasarkan definisi - tetapi cobalah dan lakukan sebaik mungkin dalam kasus Anda, yang tidak hanya fungsional, tetapi juga praktik yang baik.

hyperfekt
sumber
0

Saya sendiri, saya pasti akan melihat kemampuan bahasa yang dimaksud untuk mengakses beberapa bentuk database. Sebagian besar peristiwa yang terjadi untuk mengubah keadaan dunia secara bersamaan hanya akan direkam ke disk, dan tidak akan mempengaruhi pemain saat ini di dalam ruangan saat ini (di luar keadaan khusus seperti ledakan, atau dalam MMO, saklar yang membuka pintu dari jarak jauh, teriakan pemain lain, dll).

Dengan demikian, klien saat ini benar-benar hanya perlu menyadari Roomobjek, dan hal-hal yang mempengaruhinya secara langsung. noticableEventsOutsideRoommaka bisa saja menjadi subkelas yang Roomterpengaruh oleh perubahan terbaru ke database, dan objek game Anda menjadi jauh lebih kecil.

Ayelis
sumber
Saya mengerti bahwa pendekatan ini tidak banyak membantu dalam merintis jalan atau memicu peristiwa lokal (seperti agro di gerombolan terdekat), tetapi saya telah dikenal menyalahgunakan basis data di masa lalu ... Saya mungkin hanya akan mengirim panggilan ke update mobs set agro=1 where distance<5dan menjadi selesai dengan itu. Mungkin itu bukan praktik terbaik, tetapi itu sesuai dengan tujuan saya. Adapun pathfinding melalui database, kita selalu bisa menggunakan algoritma jalur terpendek Dijkstra ...
Ayelis
0

Solusi sebenarnya bukan dengan mengumpulkan semuanya ke objek Dunia yang besar, lalu menyebarkannya. Sebagai gantinya, Anda disarankan untuk secara akurat menentukan jenis fungsi yang Anda hadapi. Inilah beberapa contoh:

BAD:
   f :: (World, Int) -> World

Good:
   f :: (Int,Int,Int,Int) -> World

Contoh buruknya adalah mencoba untuk memodifikasi objek yang ada, tetapi contoh yang baik adalah mencoba untuk menciptakan dunia dari ruang keadaan yang dimiliki permainan Anda. Pada dasarnya, untuk menciptakan dunia, Anda perlu mengetahui semua data yang diperlukan untuk memilih dunia yang benar.

tp1
sumber