Wolfgang Thaller wrote:

 Adrian Hey wrote:

> 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).

I don't see what's so strange about that.

I think his point is that some module init actions are run while others aren't, and it's not entirely clear how the distinction should be made. Currently it's possible to think of import declarations as simply bringing names into scope, but in the presence of module init actions import declarations have side effects.


In many languages you can refer to a function in another module by its fully qualified name even if that module hasn't been explicitly imported. This is a nice feature and I'd like to see it added to Haskell, but there's a tension between it and the module-init-action feature as currently specified: we would have to look through the whole program source for fully-qualified names in order to figure out which module actions will be run.

There would be no semantic problem if all module actions were always run, but this is impossible because it would have to include modules not installed on the user's machine, as well as modules that haven't even been written yet.

There is also no semantic problem if only "safe" actions like newIORef are used, but the current proposal provides no static guarantee of this.

In any case, I have an idea of how to solve this problem -- see below.

> 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.

How about (forall s. ST s)?

We can require module init actions to have a type (forall s. ST s a) instead of IO a. The compiler or RTS wraps the actions with stToIO (which is a safe function) before executing them.

Benefits:

* It's just as easy as before to allocate global refs (and global mutable arrays).
* It's still possible to perform arbitrary IO actions (e.g. FFI calls), but you have to wrap them in unsafeIOToST -- a good thing since they really are unsafe. unsafeIOToST is much safer than unsafePerformIO when used in this way.


Problems:

   * stToIO (newSTRef 'x') doesn't have type IO (IORef Char).

This problem can be solved by adopting a reform that I've wanted for a long time anyway: make IO, IORef, etc. aliases for (ST RealWorld), (STRef RealWorld), etc. instead of completely different types. Then stToIO is the identity function and we only need a single set of state-thread functions instead of the parallel IO and ST equivalents that we have currently.

-- Ben

_______________________________________________
Haskell mailing list
[EMAIL PROTECTED]
http://www.haskell.org/mailman/listinfo/haskell

Reply via email to