I would say that state is not at all a code smell if it remains small and well controlled.
This means that using monads such as State, ST, or custom, or just having a data structure containing state data that you pass in several places is not so bad. (In fact, monads are just help in doing just that!) However, having a state that is ubiquitous (yes, that means you, MO monad!) Has a bad smell.
A vivid example of this was my team, which worked on our entry for the ICFP Programming Contest 2009 (the code is available at git: //git.cynic.net/Haskell/MKVP- contest2009). We received several different modular parts:
- VM: the virtual machine that ran the simulation program
- Controllers: several different sets of routines that read the simulator output and generate new control inputs.
- Solution: create a solution file based on controller output
- Visualizers: several different sets of routines that read both input and output ports and generate some kind of visualization or log of what happened as the simulation progressed.
Each of them has its own state, and they all interact in different ways through the input and output values โโof the virtual machine. We had several different controllers and visualizers, each of which had its own different state.
The key point here was that the interiors of any particular state were limited to their own separate modules, and each module did not know anything about the existence of a state for the other modules. Any particular set of code and data with state usually consisted of only a few tens of lines with a small number of data elements in state.
All this was glued into one small function of about a dozen lines that did not have access to the interiors of any of the states and which simply called the right things in the correct order, when they went in cycles in the simulation and passed a very limited amount of external information for each module ( of course, together with the previous state of the module).
When state is used in such a limited way, and the type system prevents you from inadvertently modifying it, it is fairly easy to handle. This is one of Haskell's beauties that allows you to do this.
One answer says, "Do not use monads." From my point of view, this is exactly the opposite. Monads are a management structure that, among other things, can help you minimize the amount of code that relates to state. If you look at monadic parsers as an example, the state of the analysis (i.e., the text being analyzed, how far it got to it, any accumulated warnings, etc.) should go through every combinator used in the parser, but there will be only a few combinators who actually manipulate the state directly; everything else uses one of these several functions. This allows you to clearly see in one place all the small amount of code that can change state, and more easily understand how it can be changed, which again makes work easier.