Функциональное программирование и текстовые приключения

14

Это в основном теоретический вопрос о FP, но я возьму текстовые приключения (например, Zork старой школы), чтобы проиллюстрировать мою точку зрения. Мне бы хотелось узнать ваше мнение о том, как бы вы смоделировали симуляцию с состоянием с помощью FP.

Текстовые приключения действительно требуют ООП. Например, все «комнаты» являются экземплярами Roomкласса, у вас может быть базовый Itemкласс и интерфейсы, например, Item<Pickable>для вещей, которые вы можете нести, и так далее.

Моделирование мира в FP работает по-другому, особенно если вы хотите обеспечить неизменность в мире, который должен изменяться по ходу игры (объекты перемещаются, враги побеждены, выигрыш растет, игрок меняет свое местоположение). Я представляю один большой объект, в Worldкотором есть все: какие комнаты вы можете исследовать, как они связаны, что несет игрок, какие рычаги были задействованы.

Я думаю, что чистый подход заключается в том, чтобы передать этот большой объект любой функции и вернуть его (возможно, модифицировать). Например, у меня есть moveToRoomфункция, которая получает Worldи возвращает ее с World.player.locationизмененной в новую комнату World.rooms[new_room].visited = Trueи так далее.

Даже если это более «правильный» путь, похоже, он принуждает нас к чистоте. В зависимости от языка программирования передача этого потенциально очень большого Worldобъекта назад и вперед может быть дорогой. Кроме того, каждая функция может иметь доступ к любому Worldобъекту. Например, комната может быть доступной или нет в зависимости от рычага, сработавшего в другой комнате, потому что она может быть затоплена, но если игрок носит спасательный жилет, он может войти в него в любом случае. Монстр может быть агрессивным или нет в зависимости от того, убил ли игрок своего двоюродного брата в другой комнате. Это означает , что roomCanBeEnteredфункция требует доступа World.player.invetoryи World.rooms, describeMonsterнеобходимо получить доступ World.monstersи так далее ( в основном, вы должныпередать всю нагрузку вокруг). Мне действительно кажется, что это требует глобальной переменной, даже если это почти хороший стиль программирования, особенно в FP.

Как бы вы решили эту проблему?

pistacchio
источник
4
«В зависимости от языка программирования передача этого потенциально очень большого объекта World назад и вперед может быть дорогой». Это, вероятно, будет передано по ссылке. «Кроме того, каждой функции может потребоваться доступ к любому объекту World». Мне трудно поверить, что каждой функции нужен доступ ко всему состоянию игры.
Доваль
2
Я думаю, что исследование Криса Мартена было бы интересно, оно призвано показать, как сделать интерактивную беллетристику на декларативных языках приятной. github.com/chrisamaphone/interactive-lp
Даниэль Гратцер,
2
Возможно, вы захотите взглянуть на этот блог, описывающий авторский подход к программированию такой игры функциональным образом. Этот пост, в частности, весьма актуален.
Gallais
3
Я должен задаться вопросом, повлиял ли этот вопрос на более позднее решение @ EricLippert написать серию статей о реализации (частях) Z-машины в Ocaml ...?
Жюль
1
@Jules Ссылка на начало этой серии, для тех, кто заинтересован: ericlippert.com/2016/02/01/west-of-house
KChaloux

Ответы:

4

Имейте в виду, что функциональные языки используют структуры данных и отдельные функции вместо объектов. Например, вместо этого у вас будет набор комнат и список предметов инвентаря.

В идеале вы также должны ограничивать объем данных, которые вы предоставляете функциям, до того, сколько они на самом деле требуют, насколько это возможно, вместо того, чтобы пройти весь мир (скажем, вы извлекаете одну релевантную комнату из своего мира; конечно, трудно полностью взаимозависимые миры). отдельно). Результат будет реинкорпирован в мировую структуру данных, создавая новое состояние. Вы не можете моделировать состояние без использования состояния; как вы говорите, некоторые вещи по своей природе требуют мутации.

Большинство практических функциональных языков предоставляют способ реализовать мутацию либо напрямую (скажем, монаду ST в Haskell или переходные процессы в Clojure), либо эффективно имитировать ее (часто путем повторного использования неизмененных частей структуры данных (хорошим примером здесь являются структуры данных Clojure по умолчанию)) ). В любом случае поддерживается чистота.

Поскольку количество состояний, которые должны быть изменены, кажется ограниченным, я бы не стал слишком беспокоиться о проблемах производительности и остановился на (возможно, уже оптимизированном) наивном функциональном подходе.

Другой вариант, который я видел, будет возвращать только инструкции, чтобы изменить какую-то часть мира от ваших индивидуальных функций, а затем обновлять ваш мир в соответствии с ними. Ряд сообщений в блоге, описывающих это, доступен по адресу http://prog21.dadgum.com/23.html .

Оба эти ответа больше касаются того, как организовать изменения, чем не передавать весь ваш мир функциям, потому что совершенно взаимозависимый не может быть сегментирован по определению - но попытайтесь сделать это как можно лучше в вашем случае, что не только функциональный, но и хорошая практика.

hyperfekt
источник
0

Сам я бы определенно изучил способность рассматриваемого языка получить доступ к какой-либо форме базы данных. Большинство событий, которые одновременно изменяют состояние мира, просто записываются на диск и не влияют на текущего игрока в текущей комнате (за исключением особых обстоятельств, таких как взрывы или в MMO, переключатели, открывающие двери удаленно, крики других игроков и т. д.).

Таким образом, текущий клиент действительно должен знать только об Roomобъекте и вещах, которые непосредственно на него влияют. noticableEventsOutsideRoomзатем может быть просто подклассом Roomзатронутых недавними изменениями в базе данных, и ваш игровой объект стал намного меньше.

Ayelis
источник
Я понимаю, что этот подход мало помогает для поиска пути или запуска локальных событий (например, агро на ближайших мобах), но в прошлом я знал, что злоупотреблял базами данных ... Я бы, наверное, просто отправил вызов update mobs set agro=1 where distance<5и был покончено с этим. Может быть, это не лучшая практика, но она подходит для моих целей. Что касается поиска пути через базу данных, всегда можно использовать алгоритм кратчайшего пути Дейкстры ...
Ayelis
0

Настоящее решение не в том , чтобы собрать все в большой объект Мира, а затем передать его. Вместо этого рекомендуется точно указать тип функции, с которой вы имеете дело. Вот несколько примеров:

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

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

Плохой пример - попытка изменить существующий объект, но хороший пример - попытка создать мир из пространства состояний вашей игры. По сути, для создания мира вам необходимо знать все данные, необходимые для выбора правильного мира.

ТР1
источник