
Lennart Augustsson wrote:
I was going to respond, but Cale very eloquently said most of what I was thinking.
I don't think eloquent is the word I would use, but I'm certainly glad you didn't feel the need to repeat all that. It'd be really nice if just for once the "global mutable state is evil" folk could give unsafePerformIO hack users the benefit of the doubt, not start out from the presumption that we are a bunch of lazy incompetent retards who are badly in need of education in the rudiments of the IO monad and (allegedly) good programming practice in general. These arguments about the language needing to protect users from doing dangerous things (specifically creating top level mutable state) are quite wrong headed. Concurrency can be dangerous too, so should we lose it? Top level mutable state can be a way of *gaining safety*, not losing it. What really frustrates me about all this is that AFAIK there are no significant technical or theoretical reasons why we can't get this safety (without resort to the unsafePerformIO hack). The only serious obstacle seems political, with this very strange but apparently widespread dogma about so called "global variables" being inherently evil, regardless of the circumstances or purpose for which they are used. With regard to Roberts post, I don't want too say much other than Robert is the first person to provide an answer to my question. I hope I'm not mis-representing his views, but I believe Robert objects to the existence of IO libraries that could not be implemented in Haskell (in principle). I.E. those that implicitly reference top level mutable state. I might be missing something, but AFAICS just about all the current IO libraries fall into this category. To take a specific example, look at the socket API. None of the functions there take any kind of OS or network sub-system state handle as an explicit argument. So it seems to me that either the implementation is entirely stateless, all the way down to peeking and poking the registers of Ethernet MACs, DMA controllers and wotnot (unlikely), or it's sneakily accessing top level mutable state in some extremely devious and non-transparent manner (heaven forbid). Or put another way, would it be possible to implement the socket API, exactly as it currently is, entirely in Haskell, starting with nothing but hardware? I don't believe it is possible, but perhaps somebody can show me I'm wrong.
Let me just add one thing. Sometimes you hear the argument "I need a global IORef here because it's to track the use of my single screen" (or keyboard, or elevator, or some some other gizmo in th real world).
No, this is not the justification for the creation of top level TWI's. This is the justification for not requiring that the API that mutates a particular top level TWI state takes that state as an explicit argument. There's no point if there is (and can be) only one of them. This is why you don't have to pass an OS state handle to every IO function that interacts with "the" OS (note singular). But even if there are two or more, you still need some mechanism to ensure that you have precisely 1:1 correspondance between physical devices and device state TWI's and/or device driver threads. This more or less prevents a robust API allowing unconstrained creation of new device state TWI's. It's far safer and simpler to provide top level TWIs (state handles) as *abstract data types* (not IORefs!). This is no different from (or less safe than) having stdout appear at the top level. Even if there is an unknown (at compile time) number of such devices and instead they are discovered somehow at boot time, you still need to maintain a some kind of finite pool of these device states, which is itself necessarily a unique TWI. IME, the approach you take to these kinds of problems can vary depending what you know or don't know for certain about the system your working with. But you always end up using top level mutable state somewhere along the way. I can only assume folk who insist it's unnecessary (or worse) have never actually tried implementing an IO sub-system from the ground up, starting with nothing but bare hardware.
I think such decisions are just generally poor design, and it should not be done in any language. The number of physical resources that a program can control should never be assumed to be one; things change.
So is it reasonable to assume that there is only one OS? Perhaps it would be best to let individual Haskell users decide for themselves what assumptions are or are not reasonable in the context of their work. Regards -- Adrian Hey