
Adrian Hey wrote:
We've been talking about this problem for years, but nothing is ever done about it (a solution to this problem isn't even on the agenda for Haskell' AFIAK).
The problem needs talking about, it's important. My objection was the implication that top-level mutable state was the right answer to the OP's question, which my strong hunch is it isn't. I don't deny the existence of a problem here.
No. Even if we stripped away all other code apart from the Haskell rts itself (OS, device drivers etc) and performed your IO entirely in Haskell (just using peek and poke on bare hardware), you'd still need top level mutable state to implement common IO API's (e.g. The socket API, does anybody really believe this is entirely stateless?).
I'm not sure that's quite to the point. Clearly we can set up state at the top of our main action: 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? 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). Mind you, I'm not convinced it's wrong, either. I think it's a hard one.
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).
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. The former, I'm not sure. My gut feeling is that it is, too. Jules