Proposed addition to stm: atomicallyIO and afterCommit

John Lauchbury has proposed the following addition to Control.Concurrent.STM. John's proposal is below, comments welcome. Personally I'm in two minds about this. It certainly captures a useful idiom, but it fails the "don't name compositions" test, and one of the additions is just a renaming of 'return', which makes it hard to justify. Cheers, Simon ----- I find I often write STM code with the structure: do ... IO code ... v <- atomically $ do ... STM code ... if ..... then do ... STM code ... return (Left ...) else do ... STM code ... return (Right ...) case v of Left ... -> ... IO actions ... Right ... -> ... IO actions ... where I have to communicate the result of choices inside STM code to corresponding code in the outer IO monad. This means that two separate parts of the code have to be kept in sync. In this simple case it's merely irritating. With larger and more complex structures it can be quite tricky to keep right. Now I use the functions 'atomicallyIO' and 'afterCommit', and can structure the code as do ... IO code ... v <- atomicallyIO $ do ... STM code ... if ..... then do ... STM code ... afterCommit $ ... IO actions ... else do ... STM code ... afterCommit $ ... IO actions ... As you see, it allows the IO code to be placed in the appropriate part of the STM code, ready to be executed when that STM branch is committed. Here's the code to would be added to the STM module: ---------------------------------------------------------------------- -- 'atomicallyIO' and 'afterCommit' are useful syntactic sugar for a -- simple continuation paradigm. They allow IO code to be placed within -- the same block of STM code in which control decisions are made -- (avoiding the need to communicate those decisions with a data type -- sent to a subsequent matching case). atomicallyIO :: STM (IO a) -> IO a atomicallyIO ioSTM = join $ atomically ioSTM afterCommit :: IO a -> STM (IO a) afterCommit mIO = return mIO ---------------------------------------------------------------------- Obviously the code is very simple, but I still find it useful to name the paradigm, and to have the specific type declarations to catch errors.

Simon just pointed out to me that this is quite closely related, though more general: http://hackage.haskell.org/package/stm-io-hooks http://www.haskell.org/haskellwiki/New_monads/MonadAdvSTM Cheers, Simon On 24/02/2010 09:58, Simon Marlow wrote:
John Lauchbury has proposed the following addition to Control.Concurrent.STM. John's proposal is below, comments welcome.
Personally I'm in two minds about this. It certainly captures a useful idiom, but it fails the "don't name compositions" test, and one of the additions is just a renaming of 'return', which makes it hard to justify.
Cheers, Simon
----- I find I often write STM code with the structure:
do ... IO code ... v <- atomically $ do ... STM code ... if ..... then do ... STM code ... return (Left ...) else do ... STM code ... return (Right ...) case v of Left ... -> ... IO actions ... Right ... -> ... IO actions ...
where I have to communicate the result of choices inside STM code to corresponding code in the outer IO monad. This means that two separate parts of the code have to be kept in sync. In this simple case it's merely irritating. With larger and more complex structures it can be quite tricky to keep right.
Now I use the functions 'atomicallyIO' and 'afterCommit', and can structure the code as
do ... IO code ... v <- atomicallyIO $ do ... STM code ... if ..... then do ... STM code ... afterCommit $ ... IO actions ... else do ... STM code ... afterCommit $ ... IO actions ...
As you see, it allows the IO code to be placed in the appropriate part of the STM code, ready to be executed when that STM branch is committed.
Here's the code to would be added to the STM module:
---------------------------------------------------------------------- -- 'atomicallyIO' and 'afterCommit' are useful syntactic sugar for a -- simple continuation paradigm. They allow IO code to be placed within -- the same block of STM code in which control decisions are made -- (avoiding the need to communicate those decisions with a data type -- sent to a subsequent matching case).
atomicallyIO :: STM (IO a) -> IO a atomicallyIO ioSTM = join $ atomically ioSTM
afterCommit :: IO a -> STM (IO a) afterCommit mIO = return mIO ----------------------------------------------------------------------
Obviously the code is very simple, but I still find it useful to name the paradigm, and to have the specific type declarations to catch errors.

On 02/24/10 05:07, Simon Marlow wrote:
Simon just pointed out to me that this is quite closely related, though more general:
http://hackage.haskell.org/package/stm-io-hooks http://www.haskell.org/haskellwiki/New_monads/MonadAdvSTM
Because of those ideas that are already floating around, I think this one is misleading. In those, the afterCommit::IO(something)->STM(something) means even if it's not the last statement in the monad, to execute the IO when the transaction commits. Which has notable effects on the compositionality of STM (not sure about "good" or "bad" effects, but it's notable). As a coder, instead of Left and Right or atomicallyIO/afterCommit, I think I'd tend to write those functions inlined where it's readable; join{-IO-} $ atomically $ do{-STM-} ... return $ do{-IO-} ... ... (I do use that sort of which-monad-is-it comment in my code whenever it's not utterly obvious... Haskell doesn't provide any equally concise way to write those notes such that it will be checked by the compiler, sadly.) -Isaac

do ... IO code ... v <- atomicallyIO $ do ... STM code ... if ..... then do ... STM code ... afterCommit $ ... IO actions ... else do ... STM code ... afterCommit $ ... IO actions ...
Interesting idea, reminds me of the wrap combinator of cml (http://hackage.haskell.org/packages/archive/cml/0.1.3/doc/html/Control-Concu...). Simon Marlow wrote:
Personally I'm in two minds about this. It certainly captures a useful idiom, but it fails the "don't name compositions" test, and one of the additions is just a renaming of 'return', which makes it hard to justify.
I'd say resolve this and similar proposals in a generic way: add it to a separate module on top of (name-space-hierarchically speaking) Control.Concurrent.STM, e.g. Control.Concurrent.STM.Util, whatever. Cheers Ben

Simon Marlow wrote:
atomicallyIO :: STM (IO a) -> IO a atomicallyIO ioSTM = join $ atomically ioSTM
afterCommit :: IO a -> STM (IO a) afterCommit mIO = return mIO
The name "afterCommit" might be confusing. To me it suggests that the argument is added to some kind of list to be run at the end of the transaction. For example: atomicallyIO $ do .. STM .. afterCommit $ print 1 .. STM .. afterCommit $ print 2 This does would only print 2, because the return value of the first afterCommit is ignored. Twan
participants (4)
-
Ben Franksen
-
Isaac Dupree
-
Simon Marlow
-
Twan van Laarhoven