ACIO versus Execution Contexts

I think it would be nice to summarise the current state of play. This really needs to be a WiKi. Ian's ACIO proposal http://www.haskell.org//pipermail/haskell-cafe/2004-November/007680.html seems in fact to achieve much the same as the proposal I originally posted to http://www.haskell.org//pipermail/haskell/2004-November/014894.html and which Benjamin Franksen has explained and simplified here http://www.haskell.org//pipermail/haskell-cafe/2004-November/007691.html You can in fact do much the same with both. With Haskell 98, Data.Dynamic and ACIO and, if your implementation has concurrency at all, thread identifiers, I can implement execution contexts. On the other hand ACIO allows you to create variables with top-level declarations such as x <- newIORef 0 which can then be accessed in a pure way. With Execution Contexts you cannot do this; you would instead need a function getX :: IO (IORef Int) which would get the global variable x. In practice I don't think this distinction very important, since there's not much you could do with x except using the IO monad. Disadvantages of execution contexts ----------------------------------- 1. You are only allowed one value of each type in an execution context. Thus it is dangerous to try to use a global value of type (IORef Int); people would have to be advised to create a special newtype for each value. 2. They are likely to be less efficient than just allowing <- declarations at top level. Certainly they are in my implementation. The vast majority of this inefficiency can be eliminated by using hash tables, but you can't escape doing at least one hash-table lookup each time you want to access a global value. The bare minimum then would be one division by a constant followed by an indirection. 3. In a concurrent system, you also need forkIO to be modified, so that you can work out which execution context applies to you. Disadvantages of ACIO --------------------- 1. The ACIO proposal preserves one bad feature of unsafePerformIO, namely that you only have one "execution context". You cannot for example run two copies of the same program simultaneously, since they will clobber each other's global variables. 2. I don't know exactly how ACIO is to be implemented, but it looks as if at the least it requires an extension to the Haskell syntax. 3. I think that if the ACIO proposal were implemented, it would in any case be a good idea to implement execution contexts or something like them on top of it, so that people can create initialisation actions which do other things than the very strict subset allowed as ACIO actions. Questions --------- 1. Is there anything people would like to do with ACIO for which execution contexts would not be practical? 2. Which if either of the proposals would the gods responsible for implementing Haskell compilers favour?

Hello George, I've been looking at your global variables library. As far as I can tell it seems to be aimed at solving a rather different problem from that the top-level <- bindings are aimed at. So I don't think you can really compare them. One is not a substitute for the other. In particular, the purpose of top level <- bindings IMO is *not* to provide "global variables" (though they could be abused this way). If you consider the example.. userInit <- oneShot realInit ..the top level MVar created is not global. It doesn't even scope over an entire module, it's buried in a closure. This kind of use would be pretty typical I think. Even when you do have top level IORefs (or more complex mutable data structures) scoping over an entire module, generally they won't be exported by that module. Instead access is strictly limited to a few carefully defined IO actions, and these are exported. Also, we don't want different threads to have their own versions of these. They are for use in situations where you need to represent state associated with a genuinely unique resource (eg top level stateful C library or hardware IO device). So in such situations it's both unnecessary and downright dangerous to provide a "newResourceState" constructor and pass this to releted IO routines as a parameter. But in the absence of such a constructor, how can the state exist at all, unless one allows top level <- bindings? This is the problem I think we're trying to solve with the top level <- proposal. On Monday 29 Nov 2004 1:19 pm, George Russell wrote:
Questions ---------
1. Is there anything people would like to do with ACIO for which execution contexts would not be practical?
Yes, see above. Big problems with "execution contexts" (at least as currently proposed) IMO are.. 1- Use of multiple dictionaries breaks the uniqueness property of top level <- bindings. 2- Constraint of one dictionary entry per type is particularly severe I think, especially as it's not statically checkable. 3- Variables are anonymous and can only be "got at" via IO monad. With top level <- bindings, the bound variables are perfectly ordinary top level Haskell identifiers which can appear in other top level definitions. 4- Even an optimisted implementation will probably be very much slower than top level <- bindings.
2. Which if either of the proposals would the gods responsible for implementing Haskell compilers favour?
Dunno. But if it makes life any easier, I would be quite happy to forget about special restricted monads and just use IO instead. The resulting programs would still be sound I think, but it makes it the programmers responsibility not to do anything stupid in a top level <- binding. It seems to me two properties are highly desirable.. 1 - Observable behaviour should not depend on ordering of <- bindings in a module 2 - Observable behavior should not depend on ordering of imports in main or any other module. AFAICS all that's needed to ensure this is that top level <- bindings are reduced lazily (as with the unsafePerformIO hack). I know that's not ideal, but it's a heck of a lot better than using unsafePerformIO. We have a similar situation with finalisers at the moment. The restricted monads guarantee a third property.. 3 - Observable behaviour does not depend on reduction order of top level <- bindings *in any way*. They could be reduced at compile time say (at least with SafeIO, not sure about ACIO). This would be nice to enforce via the type system, but I could live without it. Regards -- Adrian Hey

Adrian Hey wrote:
Also, we don't want different threads to have their own versions of these. They are for use in situations where you need to represent state associated with a genuinely unique resource (eg top level stateful C library or hardware IO device)
So in such situations it's both unnecessary and downright dangerous to provide a "newResourceState" constructor and pass this to releted IO routines as a parameter. But in the absence of such a constructor, how can the state exist at all, unless one allows top level <- bindings? This is the problem I think we're trying to solve with the top level <- proposal.
I disagee - Allowing unique state is a mistake in my opinion. I want Haskell to be the operating system - how can I do this if I cannot create new process contexts. Also how can I deal with multiple pieces of hardware that are unique? The problem with your proposal is that resources are finite, not unique. You need a solution that will allow N drivers to run N pieces of hardware - one is just a special case like when your machine only has one serial port. Some machines have two, or more... (yet the drivers are written to only handle a single device, and will be initialised multiple times once for each device) Look at how Linux scans the hardware at boot time and initialises a driver for each device it finds... that means that each driver is parameterised by the IO address of the device, and can be initialised multiple times. When an OS boots it runs an initialisation routine (the 'main' of the kernel) This is just like the main in a Haskell program - there is no need for anything to be initialised _before_ main runs... The init routine calls init on each device driver in turn, finally handing control over to the scheduler, which loads the 'init' program and that starts all the user programs. I don't see any calls for unique guarantees here... Keean. Keean.

Keean Schupke wrote:
Look at how Linux scans the hardware at boot time and initialises a driver for each device it finds... that means that each driver is parameterised by the IO address of the device, and can be initialised multiple times. When an OS boots it runs an initialisation routine (the 'main' of the kernel) This is just like the main in a Haskell program - there is no need for anything to be initialised _before_ main runs... The init routine calls init on each device driver in turn, finally handing control over to the scheduler, which loads the 'init' program and that starts all the user programs. I don't see any calls for unique guarantees here...
Hear, hear! :) And to continue on the issue of device IO. If you look at real, portable operating systems, the drivers are actually parametrized on the methods to do IO to the device. The reason is that different busses require different ways to do IO (accessing a chip on ISA bus is not the same as when the chip sits in a USB device). But you want to reuse the same driver regardless of the bus, you drivers have to take the device access methods as arguments. In all the device drivers I've written there has never been anything in any driver that makes sure you don't initialize it twice. On the contrary. You need to initialize it once for each device. It's the bus driver that makes sure you get one driver per device. And the driver on top of the bus driver makes sure you get one bus driver per bus, etc. (At the very top there's only one instance (per machine), "mainbus". Creating one copy of this would be the responsibility of main in a Haskell OS.) -- Lennart

On Tuesday 30 Nov 2004 11:21 am, Keean Schupke wrote:
I want Haskell to be the operating system - how can I do this if I cannot create new process contexts. Also how can I deal with multiple pieces of hardware that are unique?
The problem with your proposal is that resources are finite, not unique. You need a solution that will allow N drivers to run N pieces of hardware - one is just a special case like when your machine only has one serial port. Some machines have two, or more... (yet the drivers are written to only handle a single device, and will be initialised multiple times once for each device)
Sorry Keean, but throughout this entire thread I have had the feeling that you are determined to misunderstand and misrepresent just about everything I have written. You then present counter arguments to the straw men you have created and seem to expect some kind of response from me. I'm sure this is all boring the pants of other Haskell list subscribers so I don't propose to continue this game. I believe I have made my position perfectly clear to anyone who actually takes the trouble to read my posts with an open mind and understand what I am saying. So I have nothing more to add. Regards -- Adrian Hey

Adrian Hey wrote:
Sorry Keean, but throughout this entire thread I have had the feeling that you are determined to misunderstand and misrepresent just about everything I have written.
Well I think I understand it quite well - of course I may be being stupid... that is always a possibility.
You then present counter arguments to the straw men you have created and seem to expect some kind of response from me.
I don't think I am constructing straw men... I believe my objections are real. Keean.

Keean Schupke
I disagee - Allowing unique state is a mistake in my opinion. I want Haskell to be the operating system - how can I do this if I cannot create new process contexts.
Why does it matter if you can't compile code for new programs at runtime, to become a part of the same process? -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/

On Tuesday 30 November 2004 11:41, Adrian Hey wrote:
In particular, the purpose of top level <- bindings IMO is *not* to provide "global variables" (though they could be abused this way). ... If you consider the example..
userInit <- oneShot realInit
..the top level MVar created is not global. It doesn't even scope over an entire module, it's buried in a closure. ... Even when you do have top level IORefs (or more complex mutable data structures) scoping over an entire module,
I don't get it: How can they be top-level without scoping over an entire module? I mean, the proposal was to have x <- action at the top-level, right? Then how can it not be visible in the whole module? What scope *does* it have, in your opinion? Ben

On Tuesday 30 Nov 2004 9:19 pm, Benjamin Franksen wrote:
I don't get it: How can they be top-level without scoping over an entire module? I mean, the proposal was to have
x <- action
at the top-level, right? Then how can it not be visible in the whole module? What scope *does* it have, in your opinion?
x scopes over an entire module, and may be reasonably described as "global" if x is exported. But in this example.. userInit <- oneShot realInit oneShot creates a top level MVar which is only accessible via userInit and no other function, so it doesn't even scope over an entire module and is certainly not exportable. With the ACIO monad the type of oneShot would be.. oneShot :: IO a -> ACIO(IO a) If userInit is exported, it could reasonably be described as "global", but this doesn't matter because as far as users are concerned it's just a perfectly ordinary side-effectful, state manipulating IO function that works it's magic by unknown means. There's no reason for them to know or care that the some of the state it manipulates is top-level Haskell state rather than "real world" state, and there's no possibility of anybody getting at the underlying MVar and accidently corrupting it. The only way it can be got at is by executing userInit. Regards -- Adrian Hey

Adrian Hey wrote:
If userInit is exported, it could reasonably be described as "global", but this doesn't matter because as far as users are concerned it's just a perfectly ordinary side-effectful, state manipulating IO function that works it's magic by unknown means. There's no reason for them to know or care that the some of the state it manipulates is top-level Haskell state rather than "real world" state,
I suppose you are right... After all I can always produce a replacement for the MVar library that includes the indirection I want...
and there's no possibility of anybody getting at the underlying MVar and accidently corrupting it. The only way it can be got at is by executing userInit.
Keean.
participants (6)
-
Adrian Hey
-
Benjamin Franksen
-
George Russell
-
Keean Schupke
-
Lennart Augustsson
-
Marcin 'Qrczak' Kowalczyk