[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