
Jules Bean wrote:
main = do sockstate <- initSocks graphstate <- initGraphics ... disposeGraphics graphstate disposeSocks sockstate exit
Voila. Mutable state which persists for our entire program.
Arguably it's a pain passing around the state explicitly. Alternatively, you can argue that passing this stuff around explicitly makes code much easier to reason about. And we know a dozen tricks to hide this stuff (reader monads, state monads, withSocketsDo-style bracket constructs).
So I don't think this is really the issue, is it?
Have you ever wondered why our IO API's don't look like this? I think there are many problems with this approach. What are the consequences of this philosophy for issues such safety, platform independence, maintainance of stable APIs?
As I understood it, the issue was more about whether or not *library* modules should be allowed to some 'set up' initialisation code to run at the beginning of 'main' to start up their own global state. I was never convinced this was a nice idea (I don't like the thought than an 'import' alone can add hidden IO actions to main).
I agree, which is why I'm not keen on the top level mdo proposal. But addressing this issue is the point of the ACIO monad proposal.
Mind you, I'm not convinced it's wrong, either. I think it's a hard one.
I've pretty much convinced it's wrong. There should be one and only one "main" from which all subsequent IO activity derives. But creating internal state in the form of mutable data structures is not an IO activity. It just so happens that at the moment the only way to do this is newIORef :: a -> IO(IORef a), but this need not be so (readIORef and writeIORef are IO activities, but newIORef isn't).
I wouldn't dispute the assertion that at the level of complete programs or processes, implementations that don't use "global variables" are possible. But this does not hold at the level of individual IO library API's. If we want to keep our software *modular* (I take we do), then we need top level mutable state.
That's assuming you feel having an explicit 'init' command and a 'withLibXYZDo' construct breaks modularity. It doesn't feel like a terrible modularity break to me. (Plenty of C libraries I've used require explicit init calls).
Indeed they do, unfortunately. In fact it was this very problem that lead me to conclude the top level mutable state is not only not "evil", but is a necessity. Having to call explicit initialisation code is a big problem in complex systems. Exactly who is responsible for initialising what? and in what order? You could make it the users responsibility to do it right at the begining of main. But this places a heavy burden on the user to fully understand the dependencies of their hardware and the software that controls it. Or you could make it the users responsibility to initialise whatever APIs they actually make use of, in no particular order. Those APIs then initialise whatever sub-systems they actually use as part of their own initialisation. But what happens if the same sub-system is used (and initialised) by two different higher level IO libs? (The second will initialisation will destroy any state that the first may have set up.) Of course it's perfectly straight forward to avoid accidental re-initialisation, but only by making use of..you know what.
Is it the use of "global variables"? Or is it the use of the unsafePerformIO hack to create them?
The latter is definitely a problem.
Yes.
The former, I'm not sure. My gut feeling is that it is, too.
If it's necessary that there is only one libWhateverState to preserve safety properties (because the state must be kept in sync with what's really being done to "the world", of which there is also only one) then what's the point of making it a parameter (the lib is not truly parameterisable by that state). Furthermore, if it is going to take this state handle as an explicit argument then you need to provide some way for users to get this state handle. This could be by.. 1 - Making it an argument of main. 2 - Exposing a newLibWhateverState constructor 3 - Exposing a getLibWhateverState "getter". Problems.. 1 Requires the type of main to depend on what IO libs are used. Also the Boot code that invokes main must get this state handle from somewhere. 2 Potentially permits 2 or more libWhateverStates to be created (in which case all bets are off re. the safety proprties I was talking about). 3 Can't be implemented without making use of..you know what. Regards -- Adrian Hey