[Haskell] Global Variables and IO initializers

Benjamin Franksen benjamin.franksen at bessy.de
Mon Nov 1 13:49:47 EST 2004


Let me add a few thoughts on the global variables problem and the proposed 
solutions.

1) I strongly disagree with ideas to execute IO actions implicitly in whatever 
defined or undefined sequence before or during main for whatever reasons. If 
initialization actions are necessary, they should always be performed 
explicitly from inside main. If modules or libraries need init actions to be 
performed, then such actions should be exported (and documented) in the 
normal way. The reasons are manyfold:

a) Explicit initialization means that the end-programmer has complete control 
over when and if such actions get executed, which is a Good Thing. It may 
very well happen, for example, that a program needs to perform some init 
action on its own before doing the one for an imported library. Or that one 
doesn't want to execute the library's init action at all because what one 
wants to use from it doesn't need the initialization.

b) Only explicitly called init actions may be parameterized. This means that 
any library that needs its initialization action to be parameterized by the 
user has to use the explicit variant anyway.

c) Implicitly executed init actions make the code harder to reason about.

d) Calling user code before main() was introduced in C++ (it is not possible 
in C). It took a while for programmers (myself included) to realize that the 
apparent elegance and convenience of this has a huge cost in maintainability, 
especially (but not only) in connection with shared libraries. AFAIK, using 
static objects with non-trivial constructors in libraries is nowadays deemed 
bad practice and rightly so. I know of one case where this has been the cause 
for inexplicable crashes when porting a library from one unix variant to 
another one. This delayed the release of the port for at least a year!

e) It has already been noted that if init actions from other modules are to be 
executed implicity, then the compiler needs to determine which module init 
actions to perform. The straight forward 'solution' is to use the import 
lists. This would imply that changing the import list of a module has 
potentially far reaching side-effects. This could lead to very obscure bugs.


2) I agree that avoiding global variables is often inconvenient. Even if we 
combine all of them into a single compound value ('globals'), at least this 
one value has to be threaded through a lot of functions that aren't in the 
least interested in them. Aside from making the code fragile against changes, 
it introduces a certain amount of noise into the code, making it harder to 
read and understand. I disagree though with what

On Wednesday 13 October 2004 00:33, John Meacham wrote:
> The issues are
> [...]
> 3) do we need it?
> [...]
> 3) yes. the
> {-# noinline :: fooVar #-}
> fooVar = unsafePerformIO $ newIORef 0
> is a very common idiom in real programs, and very difficult to work
> around not having.

It may be tedious and inconvenient to add a record of 'globals' as argument to 
all the functions involved, but difficult it is not. It is in fact so simple 
that it could be easily automated.

What I originally wanted to propose was therefore some sort of 
source-to-source program transformation that adds all the intermediate extra 
function arguments. Then I realized that this is almost exactly what the so 
called 'implicit parameters' extension to Haskell is all about, and that 
using them as a replacement for global variables has already been proposed by 
John Hughes (http://www.cs.chalmers.se/~rjmh/Globals.ps).

He notes in this paper that implicit parameters, as implemented in GHC, infect 
the types of all the involved functions with extra contexts. Although in 
principle this is exactly what we want, it implies that the addition of an 
implicit parameter (or changing its type) potentially invalidates a lot of 
function signatures (if these are given explicitly). This is unfortunate 
because it makes the code fragile and partly re-introduces the tedium we 
wanted to avoid in the first place.

What I've been asking myself is: Wouldn't it be possible for the compiler to 
silenty add the implicit parameter type constraints behind the scenes? It 
already does so for functions without a signature, so why not do it for 
functions with an explicit signature, too?

I realize that this would be a break with the Haskell tradition to *either* 
infer types *or* use the programmer given type signatures. Nevertheless, if 
this would work, we'd have a very clean *and* easily usable solution to the 
global variables problem.

Ben

P.S. I like the '?identifier' syntax for implicit parameters because it 
clearly marks such entities as dynamically bound instead of statically: you 
wouldn't even try to find the definition of such a thing in the surrounding 
scope.


More information about the Haskell mailing list