
Hi Liyang HU you wrote:
On 23/11/06, Benjamin Franksen
wrote: One answer is in fact "to make it so that Console.Write can be rolled back too". To achieve this one can factor the actual output to another task and inside the transaction merely send the message to a transactional channel (TChan):
So, you could simply return the console output as (part of) the result of the atomic action. Wrap it in a WriterT monad transformer, even.
But this would break atomicity, wouldn't it? Another call to doSomething from another task could interrupt before I get the chance to do the actual output. With a channel whatever writes will happen in the same order in which the STM actions commit (which coincides with the order in which the counters get incremented).
Another task regularly takes messages from the channel
With STM, the outputter task won't see any messages from the channel until your main atomic block completes, after which you're living in IO-land, so you might as well do the output yourself.
Yeah, right. Separate task might still be preferable, otherwise you have to take care not to forget to actually do the IO after each transaction. I guess it even makes sense to hide the channel stuff behind some nice abstraction, so inside the transaction it looks similar to a plain IO action: output port msg The result is in fact mostly indistiguishable from a direct IO call due to the fact that IO is buffered in the OS anyway.
Pugs/Perl 6 takes the approach that any IO inside an atomic block raises an exception.
Unfortunately I can't see how to generalize this to input as well...
The dual of how you described the output situation: read a block of input before the transaction starts, and consume this during the transaction. I guess you're not seeing how this generalises because potentially you won't know how much of the input you will need to read beforehand... (so read all available input?(!) You have the dual situation in the output case, in that you can't be sure how much output it may generate / you will need to buffer.)
You say it. I guess the main difference is that I have a pretty good idea how much data is going to be produced by my own code, and if it's a bit more than I calculated then the whole process merely uses up some more memory, which is usually not a big problem. However, with input things are different: in many cases the input length is not under my control and could be arbitrarily large. If I read until my buffer is full and I still haven't got enough data, my transaction will be stuck with no way to demand more input. (however, see below)
input <- hGetContent file atomic $ flip runReaderT input $ do input <- ask -- do something with input return 42
(This is actually a bad example, since hGetContents reads the file lazily with interleaved IO...)
In fact reading everything lazily seems to be the only way out, if you don't want to have arbitrary limits for chunks of input. OTOH, maybe limiting the input chunks to some maximum length is a good idea regardless of STM and whatnot. Some evil data source may want to crash my process by making it eat more and more memory... So, after all you are probably right and there is an obvious generalization to input. Cool. Cheers Ben