Global variables?

Jon Cast jcast@ou.edu
Sun, 02 Feb 2003 21:11:56 -0600


Claus Reinke <claus.reinke@talk21.com> wrote:
> > > > import IORef
> > > > import IOExts
> > > >
> > > > globalVar :: IORef Int
> > > > globalVar = unsafePerformIO $ newIORef 0

> > > John Hughes wrote a nice pearl on the subject, see

> > > http://www.math.chalmers.se/~rjmh/Globals.ps

> > This paper claims ``unsafePerformIO is unsafe''.  That's not
> > actually true in the sense meant; unsafePerformIO merely has safety
> > pre-conditions that the compiler can't check.

> Which is the main sense in which the 'unsafe' prefix is usually meant
> to be interpreted, and that is bad enough (see below, then re-read
> John's quote of Simon PJ's description of unsafePerformIO;-). In
> particular, the 'unsafe'-prefix is not a hint for the implementation
> to treat something with extra care, but a hint for the programmer that
> the implementation may shake unsafe expressions around like any other
> ones (inlining, cse, ..), even though that is bound to lead to
> problems with the hidden side-effects.

So you have to be careful.  If that weren't what I said, I'd disagree
with you :)

> It is the programmer's responsibility to verify that none of these
> problems matter in the particular case of usage. Since many advances
> in compiler technology tend to invalidate those verifications, it is
> almost impossible to guarantee safety in such cases

Even with sufficiently liberal use of {-# NOINLINE #-}?

> - about the best one can hope for is to identify and document
> precisely which assumptions need to be made to "guarantee"
> safety. Unfortunately, this leaves it to users to figure out whether
> the assumptions made by 'unsafe' authors (e.g., no inlining) are still
> valid at the point of use..

> Btw, when talking about unsafety in that paper, John also happens to
> point out the other little problem with unsafePerformIO: it permits to
> break type safety (many a good spirit has stumbled over that
> "polymorphic references" problem in other functional languages).

> > The precondition (proof obligation) of unsafePerformIO is that the
> > order in which unsafePerformIOs are performed cannot affect the
> > outcome of the program.  However, in this case, ordering doesn't
> > matter: the only side effect is allocation of a new IORef, and
> > IORefs are sufficiently opaque we don't care (or really know) about
> > un-allocated IORefs while the only case we care about the
> > now-allocated IORef is when we de-reference it.  But, that forces
> > the IORef, which executes the unsafePerformIO.  So, whenever we
> > access the variable, it is allocated.  Therefore, the outcome of the
> > program (regardless of the order of evaluation) is the same as if
> > all such global variable declarations are executed before main
> > begins executing.  So, the outcome is independent of the order of
> > evaluation.

> > There you go: the precondition of unsafePerformIO is satisfied, so
> > the usage is safe.

> There you went.. into one of the many available traps in this
> mine-field:

> You argue that unallocated IORefs don't matter as long as "the" IORef
> is allocated before it is dereferenced. But that's just part of the
> problem - what about inlining globalVar, creating multiple IORefs?

{-# NOINLINE globalVar #-} :)

> Remember that, by using unsafePerformIO, you've given the compiler the
> license to treat globalVar as an expression without side-effects, and
> for those inlining is a common first step to enable further
> optimisations (last time I checked, the language report didn't even
> guarantee the sharing on which the globalVar trick depends)! Now there
> are multiple side-effects instead of a single one, and read-and
> write-accesses are spread out over the multiple copies of your IORef,
> none of which is likely to hold the value you'd like it to have..

> Cheers,
> Claus

Jon Cast