[Haskell-cafe] Re: [Haskell] Re: Global Variables andIO initializers

Claus Reinke claus.reinke at talk21.com
Mon Nov 8 19:32:18 EST 2004


> > >The problem is simple enough to restate for anyone who's interested.
> > >"Provide a simple reliable mechanism to ensure that in a given
> > > program run one particular top level IO operation cannot be executed
> > > more than once."
> > No language can guarantee this -  all I have to do is run 2 copies of
> > the executable at once... or wven sequentially!
> Read what I wrote :-)

oh well, if you insist on overspecification:

    Loading package base ... linking ... done.
    Prelude> let once io = getContents >> io
    Prelude> let init = once $ putStrLn "okay"
    Prelude> init
    okay
    Prelude> init
    *** Exception: <stdin>: hGetContents: illegal operation (handle is closed
    Prelude> init
    *** Exception: <stdin>: hGetContents: illegal operation (handle is closed
    Prelude>

this method may have some unexpected side-effects, but you don't mind
that, do you?-)

unsafePerformIO is a wonderful extension hook for making Haskell implementations
do things they wouldn't normally do without having to write such an implementation
from scratch. the problem is that those extended Haskell implementations may then
do things they wouldn't normally do..

for instance, you shouldn't bet your life on your method working with all future 
Haskell implementations - you'll have to check with every new release whether
your use of the extension hook is still compatible with whatever other progress
has been made (e.g., a distributed implementation may decide to start with copies
of the code on each node, etc.).

today you can say {-# please don't mess with this #-}, or if your compiler is a bit
more eager, you may have to say {-# please, please don't mess with this #-}, and
it may just work most of the time as long as everybody remembers that there are
these user-defined extensions hanging around that will break in horrible ways if we
forget that we've left the domain of pure functional programming.

i thought the point of this thread was to look for a way to take one particular use
pattern of unsafePerformIO that is deemed to be safe, and to devise a proper
language extension that captures exactly this use pattern in such a way that no 
unsafe constructs need be involved anymore.

iirc, this use pattern started out as being global variables, then became IO 
initialisers, then IO initialisers per module, then commutative monads, then
merging of  IO and ST, then run-once code, ..

you won't be able to capture all uses of unsafePerformIO unless you recreate 
it, which is exactly what you don't want - it is there already, and you want to
find ways not having to use it.

your example is still useful because it describes a situation at the borderline 
between the functional and IO worlds where one is tempted to use global
variables. as has been pointed out, the reason in this particular case is that 
one might want to do something in Haskell-land that should perhaps be done 
in the outside world, because the whole point of the exercise is to make 
something behave as a functional object when it is not. 

now one could argue that things should be converted to a functional point 
of view before importing them into Haskell, or one could argue that as much
as possible should be done on the Haskell side, even if that means 
compromising the language a little or balancing the library author over 
an abyss.both arguments have their merit.

afaik, the main problem that people try to solve with global variable tricks
is not executing code (you could call an init action in main), but having to
distribute the results of running that code. as others have pointed out, that
is similar to the situation with stdin/etc - you want to open the channels
*and* make the resulting handles available everywhere.

now, if every module by default had a stdinitMVar, you could do your
initialisation in main and put the results into Main.stdinitMVar. and if you
wanted to forward the information to an imported module, you could
put the info into Module.stdinitMVar. and if you wanted per-module
initialisation, you'd use Main.init to call Module.init (name init just a 
convention), which would put its results into its very own 
Module.stdinitMVar. problem solved. 

problem solved? i'm not so sure about that, for the same reasons
global variables/registers/etc. have been considered evil by many of
who reinvented them.and shouldn't multiple instances of modules be
possible, each with its own stdinitMVar? but some of the proposals 
that have been circulating in this thread are even worse, as they 
include an arbitrary number of user-defined and -named initialisation 
variables, and arbitrary numbers of initilisation actions, to be called 
in some underspecified form and sequence, making them hard to 
predict and find for those having to maintain such code.

there are actually at least *two* problems you need to solve: one
is providing for those few cases where global-variable-like things
are too convenient to consider anything else. that's actually fairly
easy. the other is to make sure that the cases in which people 
consider using your mechanism are as limited as possible, for 
otherwise people will use them for everything (like the IO monad).

that temptation is there because such things are too convenient
at the start to be worried about the terrible inconveniences that
appear later. it is this second problem people have failed to solve
so far in every variation of the scheme. which is why so many in
this thread have been burned by someone who abused one of 
those wonderfully convenient mechanisms.

examples from non-functional languages have been mentioned.
another is Erlang, where (by convention) processes are instances
of modules which (by convention) tend to have init functions,
and each process has a process dictionary (a collection of 
process-local variables). that feature used to be very popular, 
but its use is now heavily discouraged (although they have the
additional difficulty of not distinguishing between IO and non-IO
code..):

http://www.erlang.se/doc/programming_rules.shtml#REF18861

hth,
claus





More information about the Haskell-Cafe mailing list