Je dirais que l'État en général n'est pas une odeur de code, tant qu'il reste petit et bien contrôlé.
Cela signifie que l'utilisation de monades telles que State, ST ou des monades personnalisées, ou le simple fait de disposer d'une structure de données contenant des données d'état que vous faites circuler à plusieurs endroits, n'est pas une mauvaise chose. (En fait, les monades sont une aide pour faire exactement cela !) Cependant, avoir un état qui va partout (oui, cela veut dire toi, monade IO !) est une mauvaise odeur.
Un exemple assez clair de ceci a été lorsque mon équipe travaillait sur notre entrée pour le concours de l Concours de programmation ICFP 2009 (le code est disponible à git://git.cynic.net/haskell/icfp-contest-2009). Nous nous sommes retrouvés avec plusieurs parties modulaires différentes :
- VM : la machine virtuelle qui a exécuté le programme de simulation.
- Contrôleurs : plusieurs ensembles différents de routines qui lisent la sortie du simulateur et génèrent de nouvelles entrées de contrôle.
- Solution : génération du fichier de solution basé sur la sortie des contrôleurs.
- Visualisateurs : plusieurs ensembles différents de routines qui lisent les ports d'entrée et de sortie et génèrent une sorte de visualisation ou de journal de ce qui se passe au fur et à mesure de la simulation.
Chacun d'entre eux possède son propre état, et ils interagissent tous de diverses manières par le biais des valeurs d'entrée et de sortie de la VM. Nous avions plusieurs contrôleurs et visualiseurs différents, chacun ayant son propre type d'état.
Le point clé ici était que les internes de tout état particulier étaient limités à leurs propres modules particuliers, et chaque module ne savait rien de l'existence même de l'état des autres modules. Tout ensemble particulier de code et de données avec état ne comptait généralement que quelques dizaines de lignes, avec une poignée d'éléments de données dans l'état.
Tout cela a été collé ensemble dans une petite fonction d'environ une douzaine de lignes qui n'avait aucun accès aux internes de l'un des états, et qui a simplement appelé les bonnes choses dans le bon ordre comme il a bouclé à travers la simulation, et transmis une quantité très limitée d'informations extérieures à chaque module (avec l'état précédent du module, bien sûr).
Lorsque l'état est utilisé de manière aussi limitée, et que le système de types vous empêche de le modifier par inadvertance, il est assez facile à gérer. C'est l'une des beautés de Haskell que de vous permettre de le faire.
Une réponse dit : "N'utilisez pas les monades." De mon point de vue, c'est exactement l'inverse. Les monades sont une structure de contrôle qui, entre autres choses, peut vous aider à minimiser la quantité de code qui touche à l'état. Si l'on prend l'exemple des analyseurs syntaxiques monadiques, l'état de l'analyse (c'est-à-dire le texte en cours d'analyse, l'état d'avancement de l'analyse, les avertissements qui se sont accumulés, etc. ) doit passer par tous les combinateurs utilisés dans l'analyseur syntaxique. Pourtant, seuls quelques combinateurs manipulent directement l'état ; tout le reste utilise l'une de ces quelques fonctions. Cela vous permet de voir clairement et en un seul endroit toute la petite quantité de code qui peut modifier l'état, et de raisonner plus facilement sur la façon dont il peut être modifié, ce qui facilite encore une fois la gestion.