
Ralf Hinze
Pavel G. Zhbanov wrote:
Is it even possible to make a "global variable" in Haskell? If yes, how?
The usual fudge is:
import IORef import IOExts
globalVar :: IORef Int globalVar = unsafePerformIO $ newIORef 0
However, beware of creating more than one "global variable" of the same type. If you enable optimisation, common subexpression elimination may result in both names referring to the same IORef.
John Hughes wrote a nice pearl on the subject, see
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. However, there's nothing wrong with using it (or wrapping it) as long as the preconditions are checked. In fact, IMO, it's /better/ to use unsafePerformIO in a common case like this, because exactly when the preconditions are satisfied will be much better understood. 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.
Cheers, Ralf
Jon Cast

import IORef import IOExts
globalVar :: IORef Int globalVar = unsafePerformIO $ newIORef 0
John Hughes wrote a nice pearl on the subject, see
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. 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 - 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? 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
participants (2)
-
Claus Reinke
-
Jon Cast