Supposons que vous êtes en train de construire une assez grande simulation en Haskell. Il ya beaucoup de différents types d'entités, dont les attributs de mise à jour de la simulation progresse. Disons, par exemple, que vos entités sont appelées des Singes, des Éléphants, des Ours, etc..
Quelle est votre méthode préférée pour le maintien de ces entités des etats-unis?
La première et la plus évidente de l'approche que j'ai pensé a ceci:
mainLoop :: [Monkey] -> [Elephant] -> [Bear] -> String
mainLoop monkeys elephants bears =
let monkeys' = updateMonkeys monkeys
elephants' = updateElephants elephants
bears' = updateBears bears
in
if shouldExit monkeys elephants bears then "Done" else
mainLoop monkeys' elephants' bears'
C'est déjà moche d'avoir chaque type d'entité est explicitement mentionné dans l' mainLoop
signature de fonction. Vous pouvez imaginer comment il se absolument horrible si vous avez, disons, 20 types d'entités. (20 n'est pas déraisonnable pour des simulations complexes.) Donc, je pense que c'est une démarche inacceptable. Mais sa grâce qui sauve, c'est que des fonctions comme updateMonkeys
sont très explicites en ce qu'ils font: Ils ont une liste de Singes et de retourner un nouveau.
Alors la prochaine pensée serait de rouler le tout dans une grande structure de données qui contient de l'etat, donc le nettoyage de la signature de l' mainLoop
:
mainLoop :: GameState -> String
mainLoop gs0 =
let gs1 = updateMonkeys gs0
gs2 = updateElephants gs1
gs3 = updateBears gs2
in
if shouldExit gs0 then "Done" else
mainLoop gs3
Certains suggèrent de nous envelopper GameState
dans un État de Monade et appelez - updateMonkeys
etc. en do
. C'est très bien. Certains suggèrent plutôt nous les nettoyer avec la fonction de composition. Aussi bien, je pense. (BTW, je suis un novice avec Haskell, donc peut-être que je me trompe sur certains de cela.)
Mais le problème est, les fonctions comme updateMonkeys
ne pas vous donner de l'information utile à partir de leur type de signature. Vous ne pouvez pas vraiment être sûr de ce qu'ils font. Bien sûr, updateMonkeys
est un nom descriptif, mais c'est une maigre consolation. Lorsque je passe un dieu de l'objet et de dire "s'il vous plaît mettre à jour mon état global," je me sens comme nous sommes de retour à l'impératif monde. Il se sent comme les variables globales par un autre nom: Vous avez une fonction qui fait quelque chose à l'état, vous l'appelez, et vous de l'espoir pour le meilleur. (Je suppose que vous avez toujours éviter certains problèmes de simultanéité qui serait présent avec des variables globales dans un programme impératif. Mais meh, la concurrence n'est pas loin d'être la seule chose de mal avec les variables globales.)
Un autre problème est le suivant: Supposons que les objets doivent interagir. Par exemple, nous avons une fonction comme ceci:
stomp :: Elephant -> Monkey -> (Elephant, Monkey)
stomp elephant monkey =
(elongateEvilGrin elephant, decrementHealth monkey)
Dire ce qui est appelé en updateElephants
, parce que c'est là que nous vérifions pour voir si les éléphants sont en piétinant gamme de singes. Comment avez-vous élégamment propager les modifications à la fois les singes et les éléphants dans ce scénario? Dans notre deuxième exemple, updateElephants
prend et retourne un dieu de l'objet, de sorte qu'il pourrait en effet à la fois des changements. Mais cela brouille les eaux plus loin et renforce mon point de vue: Avec le dieu objet, vous êtes effectivement juste la mutation des variables globales. Et si vous n'utilisez pas le dieu objet, je ne suis pas sûr de savoir comment vous feriez propager ces types de changements.
Que faire? Certes, beaucoup de programmes ont besoin pour gérer la complexité de la situation, donc je suppose que il y a certains bien connus des approches à ce problème.
Juste pour le plaisir de comparaison, voici comment je pourrais résoudre le problème dans le monde de la programmation orientée objet. Il n'y aurait Monkey
, Elephant
, etc. objets. J'aurais probablement les méthodes de la classe à faire des recherches dans l'ensemble de tous les animaux vivants. Peut-être que vous pourriez rechercher par emplacement, par ID, que ce soit. Merci pour les structures de données sous-jacentes les fonctions de recherche, ils avaient séjour allouées sur le tas. (Je suis en supposant GC ou de comptage de référence.) Leur membre variables se muté tous les temps. N'importe quelle méthode de la classe serait capable de muter un animal vivant de toute autre classe. E. g. un Elephant
pourrait avoir un stomp
méthode de décrémenter la santé d'un passé- Monkey
objet, et il n'y aurait pas besoin de passer que
De même, dans un Erlang ou autres acteur-conception orientée, vous pouvez résoudre ces problèmes assez élégamment: Chaque acteur conserve sa propre boucle, et donc sa propre état, de sorte que vous n'avez jamais besoin d'un dieu de l'objet. Et de la transmission de message permet à un objet d'activités à déclencher des changements dans d'autres objets, sans passer par un tas de choses tout le chemin du retour, la pile d'appel. Pourtant, j'ai entendu dire que les acteurs de Haskell sont désapprouvées.
Enfin, toutes mes excuses pour ce mur de texte. Mon espoir est que la lenteur de cette question se justifie par son utilité à mes collègues Haskell débutants.