Re: [Haskell] Re: Global Variables and IO initializers
On Sunday 07 November 2004 13:36, you wrote:
AFAICS the only alternative to..
userInit <- oneShot realInit
is to export realInit, have users create their own userInit, and then pass that around as an argument to everything that might make use of userInit.
Yes. For instance, user code executes handle <- YourModule.init and in the rest of the program the handle is used as an argument to those and only those routines that actually depend on the initialized state represented by 'handle'. In the Haskell Web Server it turned out that after all it wasn't that many routines that depended on initialized state. It's a similar advantage as using the IO monad has over allowing arbitrary side-effects in functions: The IO monad gives you a clear separation between stuff that has (side-) effects (i.e. depends on the real word) and pure functions (which don't). Abandoning global variables gives you a clear separation of stuff that depends on initialized state and other stuff that does not depend on it.
Maybe I'm missing something, but this doesn't seem very attractive to me as a library writer (it means I must expose realInit and just trust users to only use it once).
Your realInit is an IO action, right? Imagine you are a library user, and the library exports some IO action for you to use. Would you assume that it makes no difference whether you call it once or twice in the program? Surely you won't: You know that IO actions have (side-) effects, so you would take care that the actions get executed as many times as is apropriate. If the library docs indicate that it makes no sense to call it twice, why would you do so? Maybe *I* am missing something, but I can't see any principle difference between exporting 'realInit' and exporting 'putString'. In both cases the program behaves differently depending on whether I call it once or twice.
It doesn't seem very attractive to users either (considerably complicates their code and places the burden on them to "get it right").
It may seem so at first, but I think it's a delusion. I found out with the HWS, that it does *not* necessarily "considerably complicate" the (user) code. And the compiler helps you a lot to "get it right", much more in fact than if you hide state dependency inside global variables. In the above example, the compiler *forces* you to call 'YourModule.init' because it is the only way to produce a 'handle' and you need some 'handle' to call the routines that depend on it. It also forces you to call it *before* you use any of the routines that depend on it. All in all this lifts a lot of the burdon off the user, rather than placing it on her. At the moment I cannot imagine a well designed library interface where user code would be considerably complicated if no global variables were used. But maybe you have a good example at hand to prove that this is merely due to lack of imagination on my side, and that I was extremely lucky with the HWS? ;-) Cheers, Ben
On Sunday 07 Nov 2004 1:45 pm, Benjamin Franksen wrote:
It's a similar advantage as using the IO monad has over allowing arbitrary side-effects in functions: The IO monad gives you a clear separation between stuff that has (side-) effects (i.e. depends on the real word) and pure functions (which don't). Abandoning global variables gives you a clear separation of stuff that depends on initialized state and other stuff that does not depend on it.
I don't agree. Hidden dependencies are a fact of life with stateful programming in general and IO monad in particular. Making some references explicit arguments (as you seem to be suggesting) does not eliminate the problem, it merely complicates an api for no good reason. Hiding internal state dependencies is a *good thing*. The trick is organise the dependencies and provide a robust "idiot proof" api so that users don't have to know about the internal organisation and any dependencies. I don't believe this is a new (or controversial) idea. Its the basic idea behind stateful modular or OO programming. All the user sees is a set of actions which collectively deliver on a promise (by unknown means).
Maybe I'm missing something, but this doesn't seem very attractive to me as a library writer (it means I must expose realInit and just trust users to only use it once).
Your realInit is an IO action, right?
Yes.
Imagine you are a library user, and the library exports some IO action for you to use. Would you assume that it makes no difference whether you call it once or twice in the program?
No, of course not.
Surely you won't: You know that IO actions have (side-) effects, so you would take care that the actions get executed as many times as is apropriate. If the library docs indicate that it makes no sense to call it twice, why would you do so?
Given such a statement about realInit you wouldn't (or to be more precise, given a statement that calling it twice or more will really screw things up). But the question is *how* is the user to ensure that it is only called once. I see no other way than the darned awkward alternative I gave. I suppose the other alternative is the noddy realInit is only used once in an action which is only used once, in an action .. from main (which is only used once hopefully). Is this what you have in mind? If so I don't see that as a viable option for anything but the simplest programs, and even this assumes there are no other hidden uses of realInit in any other libraries.
Maybe *I* am missing something, but I can't see any principle difference between exporting 'realInit' and exporting 'putString'. In both cases the program behaves differently depending on whether I call it once or twice.
Yes you are. Is this really your view? Or you just arguing for the sake of it? The behaviour of (and consequent constraints on correct useage of) realInit and putString are very different. Must I eloborate them?
It doesn't seem very attractive to users either (considerably complicates their code and places the burden on them to "get it right").
It may seem so at first, but I think it's a delusion.
Trust me on this, for whatever reason, it's absolutely vital that realInit is used 0 or 1 times only, 2 or more is a catastrophic error. So I'll ask again. Please provide a simpler _and_ safer alternative (some real Haskell code please).
At the moment I cannot imagine a well designed library interface where user code would be considerably complicated if no global variables were used. But maybe you have a good example at hand to prove that this is merely due to lack of imagination on my side, and that I was extremely lucky with the HWS? ;-)
Indeed, I believe this is the case. I'm guessing of course, but I imagine all your IO is done via standard Haskell library calls (socket API or whatever), in which case they will hide a lot of the stateful compexity of their implementation already. If so it seems to me you're using the fact that somebody has already solved the problem for you as an argument that no solution is necessary. (It would be interesting to see what the api's of the libraries you're using would look like, if they had been designed according to the principles you're advocating). Regards -- Adrian Hey
participants (2)
-
Adrian Hey -
Benjamin Franksen