[Haskell-cafe] global variables

Adrian Hey ahey at iee.org
Fri May 18 09:34:45 EDT 2007


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




More information about the Haskell-Cafe mailing list