Programación funcional y aventuras de texto.

14

Esta es principalmente una pregunta teórica sobre FP, pero tomaré aventuras de texto (como Zork de la vieja escuela) para ilustrar mi punto. Me gustaría conocer sus opiniones sobre cómo modelaría una simulación con estado con FP.

Las aventuras de texto realmente parecen requerir POO. Por ejemplo, todas las "salas" son instancias de una Roomclase, puede tener una Itemclase básica e interfaces como Item<Pickable>cosas que puede transportar, etc.

El modelado mundial en FP funciona de manera diferente, especialmente si quieres imponer la inmutabilidad en un mundo que debe mutar a medida que avanza el juego (los objetos se mueven, los enemigos son derrotados, la puntuación aumenta, el jugador cambia su ubicación). Me imagino un solo gran objeto Worldque lo tiene todo: cuáles son las habitaciones que puedes explorar, cómo están vinculadas, qué lleva el jugador, qué palancas se han activado.

Creo que un enfoque puro sería básicamente pasar este gran objeto a cualquier función y hacer que lo devuelva (posiblemente modificado). Por ejemplo, tengo una moveToRoomfunción que obtiene Worldy devuelve con World.player.locationcambiado a la nueva sala, World.rooms[new_room].visited = Truey así sucesivamente.

Incluso si esta es la forma más "correcta", parece estar haciendo cumplir la pureza por el bien de la misma. Dependiendo del lenguaje de programación, pasar este Worldobjeto potencialmente muy grande de un lado a otro puede ser costoso. Además, cada función puede necesitar tener acceso a cualquier Worldobjeto. Por ejemplo, una habitación puede ser accesible o no dependiendo de una palanca activada en otra habitación porque puede estar inundada, pero si el jugador lleva un chaleco salvavidas, puede ingresar de todos modos. Un monstruo puede ser agresivo o no, dependiendo de si el jugador ha matado a su primo en otra habitación. Esto significa que la roomCanBeEnteredfunción necesita el acceso World.player.invetoryy World.rooms, describeMonsternecesita acceder World.monstersy así sucesivamente (básicamente, que debepasar toda la carga). Esto realmente me parece que requiere una variable global, incluso si este es un buen estilo de programación, especialmente en FP.

Como resolverías este problema?

pistacho
fuente
44
"Dependiendo del lenguaje de programación, pasar este objeto mundial potencialmente muy grande de un lado a otro puede ser costoso". Probablemente se pasará por referencia. "Además, cada función puede necesitar tener acceso a cualquier objeto del mundo". Me resulta difícil creer que cada función necesita acceso a todo el estado del juego.
Doval
2
Creo que la investigación de Chris Marten sería interesante, tiene como objetivo mostrar cómo hacer agradable la ficción interactiva en lenguajes declarativos. github.com/chrisamaphone/interactive-lp
Daniel Gratzer
2
Es posible que desee echar un vistazo a este blog que describe el enfoque del autor para programar dicho juego de manera funcional. Esta publicación en particular es bastante acertada.
gallais
3
Tengo que preguntarme si esta pregunta influyó en la decisión posterior de @ EricLippert de escribir una serie de artículos sobre la implementación (partes de) una máquina Z en Ocaml ...?
Julio
1
@Jules Un enlace al inicio de esa serie, para aquellos interesados: ericlippert.com/2016/02/01/west-of-house
KChaloux

Respuestas:

4

Tenga en cuenta que los lenguajes funcionales utilizan estructuras de datos y funciones separadas en lugar de objetos. Por ejemplo, tendría un conjunto de habitaciones y una lista de artículos de inventario como un mundo.

Idealmente, también limitaría la cantidad de datos que da a las funciones a la cantidad que realmente requieren tanto como sea posible en lugar de pasar por todo el mundo (digamos que extrae una sola habitación relevante de su mundo; por supuesto, mundos completamente interdependientes pueden ser difíciles de separar). El resultado sería reincorporado a la estructura de datos mundial, creando un nuevo estado. No puede modelar el estado sin usar el estado; como dices, algunas cosas requieren inherentemente mutación.

La mayoría de los lenguajes funcionales prácticos proporcionan una forma de realizar la mutación directamente (por ejemplo, la mónada ST en Haskell o transitorios en Clojure) o simularla de manera eficiente (a menudo reutilizando partes no modificadas de la estructura de datos (las estructuras de datos predeterminadas de Clojure son un buen ejemplo aquí) ) De cualquier manera se mantiene la pureza.

Dado que la cantidad de estado que necesita ser mutada parece limitada, no me preocuparía demasiado por los problemas de rendimiento e iría con el enfoque funcional ingenuo (posiblemente ya optimizado).

Una opción diferente que he visto sería devolver solo instrucciones para cambiar una parte del mundo de sus funciones individuales, y luego actualizar su mundo de acuerdo con estas. Una serie de publicaciones de blog que describen esto está disponible en http://prog21.dadgum.com/23.html .

Ambas respuestas tratan más sobre cómo organizar los cambios que no pasar todo su mundo a las funciones, porque uno perfectamente interdependiente no puede segmentarse por definición , pero intente hacerlo de la mejor manera posible en su caso, lo cual no es solo funcional, pero también buena práctica.

hyperfekt
fuente
0

Yo mismo, definitivamente examinaría la capacidad del idioma en cuestión para acceder a algún tipo de base de datos. La mayoría de los eventos que cambian el estado del mundo al mismo tiempo simplemente se grabarían en el disco y no afectarían al jugador actual dentro de la sala actual (fuera de circunstancias especiales como explosiones o en un MMO, interruptores que abren puertas remotamente, gritos de otros jugadores, etc.

Como tal, el cliente actual solo necesita ser consciente del Roomobjeto y de las cosas que lo afectan directamente. noticableEventsOutsideRoompodría simplemente ser una subclase de Roomafectados por los cambios recientes en la base de datos, y su objeto de juego se ha vuelto mucho más pequeño.

Ayelis
fuente
Entiendo que este enfoque no hace mucho para encontrar caminos o desencadenar eventos locales (como agro en multitudes cercanas), pero se sabe que abusé de las bases de datos en el pasado ... Probablemente solo enviaría una llamada update mobs set agro=1 where distance<5y sería hecho con eso. Tal vez esa no sea la mejor práctica, pero se adapta a mis propósitos. En cuanto a la búsqueda de rutas a través de la base de datos, siempre se podría usar el algoritmo de ruta más corta de Dijkstra ...
Ayelis
0

La solución real no es recolectar todo en un gran objeto del Mundo y luego pasarlo. En cambio, se recomienda especificar con precisión el tipo de función con la que se está tratando. Aquí hay algunos ejemplos:

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

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

El mal ejemplo es tratar de modificar un objeto existente, pero el buen ejemplo es tratar de crear un mundo a partir del espacio de estado que tiene tu juego. Básicamente, para crear un mundo, debe conocer todos los datos necesarios para seleccionar el mundo correcto.

tp1
fuente