Module Initialisation? (was Re: [Haskell] (no subject))

Adrian Hey ahey at
Fri Oct 15 00:32:19 EDT 2004

On Thursday 14 Oct 2004 10:18 am, Simon Marlow wrote:
> On 13 October 2004 16:17, Wolfgang Thaller wrote:
> > We could get away with "desugaring" them to some very "unsafe" non-IO-
> > bindings and having the "module init action" do something evil to
> > make the IO happen in the right order... should be possible to make
> > that look exactly like mdo from the outside.
> > We'll end up using the unsafePerformIO hack inside the implementation
> > again, so that people end up with two IORefs instead of one, but that
> > should be cheap enough:
> >
> > foo <- someAction
> >
> > ... could be transformed into ...
> >
> > foo_var = unsafePerformIO $ newIORef (throw NonTermination)
> > foo_action = someAction >>= writeIORef foo_var
> > foo = unsafePerformIO $ readIORef foo
> >
> > ... with the appropriate NOINLINEs.
> > The module init action would then make sure that foo_action gets
> > invoked.
> Yes, we could do that.  The fact that we're using NOCSE/NOINLINE
> internally still seems very fragile, though.  Oh well, perhaps we have
> to live with that if we don't want the pain of a special binding type
> throughout the compiler.

I'm puzzled about this idea of "module init action" in a declarative
language. Perhaps, if it's desirable to have some module initialisation
applied to a module if anything from it is used, the way to do this would
be to have a reserved identifier specially for the purpose, like
"main", but at the module level. (Though this idea still seems a
bit strange to me).

Also, I'm still not convinced that mdo is something I want emulated
anyway, (well not if it means doing something like the above). If
I've interpreted this correctly this means that someAction will always
get executed, whether or not foo (or anything dependent on foo) is used
elsewhere? This seems like a bad thing to me. It may be harmless enough
if all it does is create a few IORefs which are then promptly garbage
collected, but in some situations it could involve quite complex
and expensive initialisation operations on foreign libraries
(for example).

Since a lot of the concerns expressed about this seem to centre
around possible abuse of arbitrary IO operations in these top
level constructions, maybe the problem could be addressed by
insisting that a restricted monad was used, call it SafeIO say.

So we'd have something like this:
	initIORef     :: a -> SafeIO (IORef a)
	initMVar      :: a -> SafeIO (MVar a)
	initEmptyMVar :: SafeIO (MVar a)
	liftSafeIO    :: SafeIO a -> IO a

The idea being that from within SafeIO you couldn't read or modify IORefs
or do any other IO operation, all you could do is create new ones (or
incorporate existing ones into the data structure). Wouldn't this + mdo
suffice for the safe construction and initialisation of complex mutable data
structures (which is probably all people want most of the time)?

I guess you'd still need a get out occasionally, especially for FFI..
	flakyLiftIO :: IO a -> SafeIO a

So at the top level you'd probably have..

myThing :: Thing
myThing <- safeNewThing

safeNewThing :: SafeIO Thing
safeNewThing = mdo ...

newThing :: IO Thing
newThing = liftSafeIO safeNewThing

Now that all seems so simple, I'm certain I must have overlooked
something :-(

Adrian Hey

More information about the Haskell mailing list