Is there any reason why IO should not be defined as:
type IO a = ST RealWorld a in implementations that support ST?
This way IORef/STRef and IOArray/STArray can be merged. I know under the hood they already share code, but this way they can also share an interface. Twan
On Mon, Jan 23, 2006 at 09:55:39PM +0100, Twan van Laarhoven wrote:
Is there any reason why IO should not be defined as:
type IO a = ST RealWorld a in implementations that support ST?
This way IORef/STRef and IOArray/STArray can be merged. I know under the hood they already share code, but this way they can also share an interface.
But IO isn't a state monad: others are concurrently changing the world without waiting for my Haskell program to terminate. Some compilers use a state monad to enforce sequencing, but a continuation monad or resumption monad would work just as well. However IO does include a state monad, so one could define (using IORegion instead of the presumptuous RealWorld): type IORef = STRef IORegion type IOArray = STArray IORegion type IOUArray = STUArray IORegion newIORef = stToIO . newSTRef ...
Ross Paterson wrote:
But IO isn't a state monad: others are concurrently changing the world without waiting for my Haskell program to terminate.
I think that closed-world behavior should be treated as a property of runST, not of the ST monad operations. Otherwise your IORef = STRef IORegion proposal doesn't work either, because e.g. myId :: STRef s a -> a -> ST s a myId r x = writeSTRef r x >> readSTRef r doesn't necessarily return its second argument when lifted to IO, in the presence of multithreading. This philosophical objection to IO = ST IORegion has come up before, and I think it's approaching the problem exactly backwards. If IO = ST IORegion works in practice and is useful in practice, then we ought to be coming up with a theory of IO/ST that supports it, not rejecting it on the basis of a theory that doesn't support it. -- Ben
On Tue, Jan 24, 2006 at 06:40:38PM +0000, Ben Rudiak-Gould wrote:
Ross Paterson wrote:
But IO isn't a state monad: others are concurrently changing the world without waiting for my Haskell program to terminate.
I think that closed-world behavior should be treated as a property of runST, not of the ST monad operations. Otherwise your IORef = STRef IORegion proposal doesn't work either, because e.g.
myId :: STRef s a -> a -> ST s a myId r x = writeSTRef r x >> readSTRef r
doesn't necessarily return its second argument when lifted to IO, in the presence of multithreading.
Ouch! Being able to do that kind of reasoning in ST is really important. I'd rather sacrifice stToIO than lose that.
On Mon, Jan 23, 2006 at 09:55:39PM +0100, Twan van Laarhoven wrote:
Is there any reason why IO should not be defined as:
type IO a = ST RealWorld a in implementations that support ST?
This way IORef/STRef and IOArray/STArray can be merged. I know under the hood they already share code, but this way they can also share an interface.
ST doesn't have exceptions which IO does. It would be no good to make ST pay for the cost of exception handling. GHC handles them behind the scenes (I think?) but in jhc they are explicit and IO is defined as follows:
data World__
data IOResult a = FailIO World__ IOError | JustIO World__ a newtype IO a = IO (World__ -> IOResult a)
I belive other implementations have used continuations for IO as well. John -- John Meacham - ⑆repetae.net⑆john⑈
John Meacham wrote:
ST doesn't have exceptions which IO does. It would be no good to make ST pay for the cost of exception handling. GHC handles them behind the scenes (I think?) but in jhc they are explicit and IO is defined as follows:
data World__
data IOResult a = FailIO World__ IOError | JustIO World__ a newtype IO a = IO (World__ -> IOResult a)
Supposing you had data STResult s a where FailIO :: World__ IOWorld__ -> IOError -> STResult IOWorld__ a JustST :: World__ s -> a -> STResult s a could static analysis then eliminate the test for FailIO in ST code? It would be pretty cool if the answer was yes, since it would mean that merging IO and ST would be an optimization instead of a pessimization (the test could also be omitted in IO code that uses only the ST subset). In jhc I suppose this would have to happen late in compilation, when you eliminate unused type parameters. Actually, won't the test for FailIO always be eliminated by the existing points-to analysis? -- Ben
On Feb 20, 2006, at 5:57 PM, Ben Rudiak-Gould wrote:
John Meacham wrote:
ST doesn't have exceptions which IO does. It would be no good to make ST pay for the cost of exception handling. GHC handles them behind the scenes (I think?) but in jhc they are explicit and IO is defined as follows:
data World__
data IOResult a = FailIO World__ IOError | JustIO World__ a newtype IO a = IO (World__ -> IOResult a)
Supposing you had
data STResult s a where FailIO :: World__ IOWorld__ -> IOError -> STResult IOWorld__ a JustST :: World__ s -> a -> STResult s a
could static analysis then eliminate the test for FailIO in ST code?
Only in a very limited way. Nothing can be done with the following code: if checkOK then JustST ... else FailIO ... So we can "optimize away" the JustST from failure-free IO computations, but anything which might possibly fail is problematic. Sadly, IO computations which perform any actual IO must usually admit the possibility of failure, and you actually degrade performance in practice by wrapping things up in an extra constructor, at least compared to a mechanism where failure is propagated by invoking an alternate continuation. (You also have to carefully engineer the strictness of "bind" and of every IO construct in order to make sure that eliminating the constructor is actually a valid transformation and that things don't deadlock, but that's a simple matter of programming...) -Jan-Willem Maessen (who tried a similar experiment back in 1994 or so.)
It would be pretty cool if the answer was yes, since it would mean that merging IO and ST would be an optimization instead of a pessimization (the test could also be omitted in IO code that uses only the ST subset). In jhc I suppose this would have to happen late in compilation, when you eliminate unused type parameters.
Actually, won't the test for FailIO always be eliminated by the existing points-to analysis?
-- Ben
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On Mon, Feb 20, 2006 at 10:57:23PM +0000, Ben Rudiak-Gould wrote:
could static analysis then eliminate the test for FailIO in ST code? It would be pretty cool if the answer was yes, since it would mean that merging IO and ST would be an optimization instead of a pessimization (the test could also be omitted in IO code that uses only the ST subset). In jhc I suppose this would have to happen late in compilation, when you eliminate unused type parameters.
Hmm.. not sure, that is an interesting idea, I do do a form of 'early' type analysis to drop branches for types that are never used, but before the whole program is collected it is necessarily conservative so can't find everything. combining that analysis with GADTs might open up some new optimizations.
Actually, won't the test for FailIO always be eliminated by the existing points-to analysis?
Yeah, a lot of them are. but that doesn't happen til very late in the code generation so these FailIOs clutter up the intermediate jhc core code. I am experimenting with using longjmp(2) and setjmp(2) based exceptions in IO. though, I'd eventually like to add true c-- style continuations to grin which should obviate the need for setjmp and longjmp which currently cannot be optimized through all that well. John -- John Meacham - ⑆repetae.net⑆john⑈
participants (5)
-
Ben Rudiak-Gould -
Jan-Willem Maessen -
John Meacham -
Ross Paterson -
Twan van Laarhoven