Il s'agit principalement d'une question théorique sur la PF, mais je vais prendre des aventures textuelles (comme la vieille école de Zork) pour illustrer mon propos. J'aimerais connaître votre opinion sur la façon dont vous modéliseriez une simulation avec état avec FP.
Les aventures textuelles semblent vraiment appeler la POO. Par exemple, toutes les "salles" sont des instances d'une Room
classe, vous pouvez avoir une Item
classe de base et des interfaces comme Item<Pickable>
pour les choses que vous pouvez transporter et ainsi de suite.
La modélisation des mondes dans FP fonctionne différemment, surtout si vous voulez imposer l'immuabilité dans un monde qui doit muter au fur et à mesure que le jeu progresse (les objets sont déplacés, les ennemis sont vaincus, le score augmente, le joueur change d'emplacement). J'imagine un seul grand objet World
qui a tout: quelles sont les pièces que vous pouvez explorer, comment elles sont liées, ce que le joueur porte, quels leviers ont été déclenchés.
Je pense qu'une approche pure serait de passer ce gros objet à n'importe quelle fonction et de le faire retourner par eux (éventuellement modifié). Par exemple, j'ai une moveToRoom
fonction qui l'obtient World
et la renvoie avec une World.player.location
modification dans la nouvelle pièce, World.rooms[new_room].visited = True
etc.
Même si c'est la manière la plus "correcte", cela semble être une pureté pour le plaisir. Selon le langage de programmation, le passage de cet World
objet potentiellement très gros dans les deux sens peut être coûteux. En outre, chaque fonction peut avoir besoin d'avoir accès à n'importe quel World
objet. Par exemple, une pièce peut être accessible ou non selon un levier déclenché dans une autre pièce car elle peut être inondée, mais si le joueur porte un gilet de sauvetage, il peut y entrer quand même. Un monstre peut être agressif ou non selon que le joueur a tué son cousin dans une autre pièce. Cela signifie que la roomCanBeEntered
fonction a besoin d'accéder World.player.invetory
et World.rooms
, a describeMonster
besoin d'accéder World.monsters
et ainsi de suite ( en gros, vous devezfaire passer toute la charge). Cela me semble vraiment appeler une variable globale, même si c'est tout sauf un bon style de programmation surtout en FP.
comment résoudrais-tu ce problème?
la source
Réponses:
Gardez à l'esprit que les langages fonctionnels utilisent des structures de données et des fonctions séparées au lieu d'objets. Par exemple, vous auriez à la place un ensemble de pièces et une liste d'articles d'inventaire en tant que monde.
Idéalement, vous limiteriez également la quantité de données que vous donnez aux fonctions à ce dont elles ont réellement besoin autant que possible au lieu de traverser le monde entier (par exemple, vous extrayez une seule pièce pertinente de votre monde; bien sûr, des mondes complètement interdépendants peuvent être difficiles à séparé). Le résultat serait réintégré dans la structure de données mondiale, créant un nouvel état. Vous ne pouvez pas modéliser l'état sans utiliser l'état; comme vous le dites, certaines choses nécessitent intrinsèquement une mutation.
La plupart des langages fonctionnels pratiques fournissent un moyen de réaliser la mutation soit directement (par exemple la monade ST dans Haskell ou les transitoires dans Clojure), soit de la simuler efficacement (souvent en réutilisant des parties inchangées de la structure de données (les structures de données par défaut de Clojure sont un bon exemple ici) ). Dans tous les cas, la pureté est maintenue.
Étant donné que la quantité d'état à muter semble limitée, je ne m'inquiéterais pas trop des problèmes de performances et adopterais l'approche fonctionnelle naïve (peut-être déjà optimisée).
Une autre option que j'ai vue serait de renvoyer uniquement des instructions pour changer une partie du monde de vos fonctions individuelles, puis de mettre à jour votre monde en fonction de celles-ci. Une série d'articles de blog décrivant cela est disponible sur http://prog21.dadgum.com/23.html .
Ces deux réponses traitent plus de la façon d'organiser les changements que de ne pas passer votre monde entier aux fonctions, car un parfaitement interdépendant ne peut pas être segmenté par définition - mais essayez de le faire du mieux que vous pouvez dans votre cas, ce qui n'est pas seulement fonctionnel, mais aussi de bonnes pratiques.
la source
Moi-même, j'examinerais certainement la capacité de la langue en question à accéder à une certaine forme de base de données. La plupart des événements qui changent simultanément l'état du monde seraient simplement enregistrés sur le disque et n'affecteraient pas le joueur actuel dans la salle actuelle (en dehors de circonstances spéciales telles que des explosions ou dans un MMO, des interrupteurs qui ouvrent des portes). à distance, cris des autres joueurs, etc.).
En tant que tel, le client actuel n'a vraiment besoin que de connaître l'
Room
objet et les choses qui l'affectent directement.noticableEventsOutsideRoom
pourrait alors simplement être une sous-classe de personnesRoom
affectées par les modifications récentes de la base de données, et votre objet de jeu est devenu beaucoup plus petit.la source
update mobs set agro=1 where distance<5
et être fait avec elle. Ce n'est peut-être pas la meilleure pratique, mais cela convient à mes besoins. Quant à la recherche de chemin via une base de données, on pourrait toujours utiliser l'algorithme de chemin le plus court de Dijkstra ...La vraie solution n'est pas de tout rassembler dans un grand objet du monde, puis de le faire circuler. Au lieu de cela, il est recommandé de spécifier avec précision le type de la fonction avec laquelle vous traitez. Voici quelques exemples:
Le mauvais exemple essaie de modifier un objet existant, mais le bon exemple essaie de créer un monde à partir de l'espace d'état de votre jeu. Fondamentalement, pour créer un monde, vous devez connaître toutes les données nécessaires pour sélectionner le monde approprié.
la source