An answer and a question to GHC implementors [was Re: How to make Claessen's Refs Ord-able?]

John Meacham
Mon, 8 Apr 2002 11:33:00 -0700

On Mon, Apr 08, 2002 at 11:58:22AM +0100, Simon Marlow wrote:
> I'm afraid the answer is just "unsafePerformIO is called
> *unsafe*PerformIO for a reason"!  You're using it in an inherently
> unsafe way here - the result of the program depends on whether the
> compiler duplicates the expression or not, something which it is
> normally free to do without affecting the meaning of the program.
> However, it is possible to have global top-level references using
> unsafePerformIO if you're very careful about it.  In GHC we do something
> like this:
> {-# NOINLINE global_var #-}
> global_var :: IORef Int
> global_var = unsafePerformIO (newIORef 42)
> the NOINLINE pragma is used to ensure that there is precisely *one* copy
> of the right hand side of global_var in the resulting program (NOTE: you
> also need to compile the program with -fno-cse to ensure that the
> compiler doesn't also common up the RHS of global_var with other similar
> top-level definitions).

this usage of unsafePerformIO is such a staple of real-world Haskell
programming, it seems there should be some language (or experemental
compiler *wink wink ghc nudge*) support for it. I am not sure what form
it would take though.

One idea I was toying with that would actually just require a new
standard library function would be perhaps a 'safer' form of
unsafePerformIO called memoIO :: IO a -> IO a
which takes an arbitrary io action and returns an action which only
calls the original action once and from then on returns the exact same
value it returned the first time. it seems that this would allow much of
what people want to do with unsafePerformIO, but in a nicer way as all
io operations stay io operations, but the functional mapping of an io
action to a memoed io action can be used anywhere.

you can then do the above like

getGlobalVar :: IO (IORef Int)
getGlobalVar = memoIO (newIORef 42) 

note that this is not exactly the same since getting the global var is
in the io monad, but that really makes sense if you think about it. and
chances are you are already in IO if you need an IORef.


John Meacham - California Institute of Technology, Alum. -