b) Some predetermined order, with semantics like mdo:
Hmm, I just realized that this also means we can execute moduke intialisation code that returns no result using: _ <- do ... I like that, I desperately need that for my Objective-C binding... So the extension with the specified order actually allows one to do more things than the unsafePerformIO hack. I wouldn't care about specifying the order otherwise. If the default order is wrong, people can still use unsafeInterleaveIO.
GHC already has an initialisation procedure for each module in the program which could be adapted to run the initialisation-time IO actions, with a little effort.
Hmm, doesn't the initialisation procedure run before the Stg machine is properly set up? I guess all the GHC init code could do is to register IO actions to be executed later (but before main is run). We probably want all the GHC module init procedures to be run before we execute any IO actions.
I'm more worried about what other changes we have to make to the compiler: if we can avoid having to flag top-level bindings as monadic/non-monadic all the way through the compiler, then we could avoid a pervasive change to the compiler. At the moment I can't see an obvious way to do that.
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. So we'd only need to annotate modules with a list of init actions and extend the RTS appropriately for that... Cheers, Wolfgang -- GMX ProMail mit bestem Virenschutz http://www.gmx.net/de/go/mail +++ Empfehlung der Redaktion +++ Internet Professionell 10/04 +++
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. Cheers, Simon
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 :-( Regards -- Adrian Hey
participants (3)
-
Adrian Hey -
Simon Marlow -
Wolfgang Thaller