David Feuer
2018-07-07 18:35:24 UTC
I have proposed[1] the replacement of the atomicModifyMutVar# primop, and
the addition of two cheaper but less capable ones. It seems likely that the
proposal will succeed, but that the GHC steering committee will leave the
question of user interface changes to the libraries list. I would like to
open the discussion here.
The new primops lead naturally to several thin wrappers:
-- Atomically replace the IORef contents
-- with the first component of the result of
-- applying the function to the old contents.
-- Return the old value and the result of
-- applying the function, without forcing the latter.
--
-- atomicModifyIORef ref f = do
-- (_old, ~(_new, res)) <- atomicModifyIORef2Lazy ref f
-- return res
atomicModifyIORef2Lazy
:: IORef a -> (a -> (a, b)) -> IO (a, (a, b))
-- Atomically replace the IORef contents
-- with the result of applying the function
-- to the old contents. Return the old and
-- new contents without forcing the latter.
atomicModifyIORefLazy_
:: IORef a -> (a -> a) -> IO (a, a)
-- Atomically replace the IORef contents
-- with the given value and return the old
-- contents.
--
-- atomicWriteIORef ref x = void (atomicSwapIORef ref x)
atomicSwapIORef
:: IORef a -> a -> IO a
Based on the code I've read that uses atomicModifyIORef, I believe that the
complete laziness of atomicModifyIORef2Lazy and atomicModifyIORefLazy_ is
very rarely desirable. I therefore believe we should also (or perhaps
instead?) offer stricter versions:
atomicModifyIORef2
:: IORef a -> (a -> (a, b)) -> IO (a, (a, b))
atomicModifyIORef2 ref f = do
r@(_old, (_new, _res)) <- atomicModifyIORef2Lazy ref f
return r
atomicModifyIORef_
:: IORef a -> (a -> a) -> IO (a, a)
atomicModifyIORef_ ref f = do
r@(_old, !_new) <- atomicModifyIORefLazy_ ref f
return r
The classic atomicModifyIORef also admits a less gratuitously lazy version:
atomicModifyIORefNGL
:: IORef a -> (a -> (a,b)) -> IO b
atomicModifyIORefNGL ref f = do
(_old, (_new, res)) <- atomicModifyIORef2 ref f
return res
Should we add that as well (with a better name)? Should we even consider
*replacing* the current atomicModifyIORef with that version? That could
theoretically break existing code, but I suspect it would do so very
rarely. If we don't change the existing atomicModifyIORef now, I think we
should consider deprecating it: it's very easy to accidentally use it too
lazily.
the addition of two cheaper but less capable ones. It seems likely that the
proposal will succeed, but that the GHC steering committee will leave the
question of user interface changes to the libraries list. I would like to
open the discussion here.
The new primops lead naturally to several thin wrappers:
-- Atomically replace the IORef contents
-- with the first component of the result of
-- applying the function to the old contents.
-- Return the old value and the result of
-- applying the function, without forcing the latter.
--
-- atomicModifyIORef ref f = do
-- (_old, ~(_new, res)) <- atomicModifyIORef2Lazy ref f
-- return res
atomicModifyIORef2Lazy
:: IORef a -> (a -> (a, b)) -> IO (a, (a, b))
-- Atomically replace the IORef contents
-- with the result of applying the function
-- to the old contents. Return the old and
-- new contents without forcing the latter.
atomicModifyIORefLazy_
:: IORef a -> (a -> a) -> IO (a, a)
-- Atomically replace the IORef contents
-- with the given value and return the old
-- contents.
--
-- atomicWriteIORef ref x = void (atomicSwapIORef ref x)
atomicSwapIORef
:: IORef a -> a -> IO a
Based on the code I've read that uses atomicModifyIORef, I believe that the
complete laziness of atomicModifyIORef2Lazy and atomicModifyIORefLazy_ is
very rarely desirable. I therefore believe we should also (or perhaps
instead?) offer stricter versions:
atomicModifyIORef2
:: IORef a -> (a -> (a, b)) -> IO (a, (a, b))
atomicModifyIORef2 ref f = do
r@(_old, (_new, _res)) <- atomicModifyIORef2Lazy ref f
return r
atomicModifyIORef_
:: IORef a -> (a -> a) -> IO (a, a)
atomicModifyIORef_ ref f = do
r@(_old, !_new) <- atomicModifyIORefLazy_ ref f
return r
The classic atomicModifyIORef also admits a less gratuitously lazy version:
atomicModifyIORefNGL
:: IORef a -> (a -> (a,b)) -> IO b
atomicModifyIORefNGL ref f = do
(_old, (_new, res)) <- atomicModifyIORef2 ref f
return res
Should we add that as well (with a better name)? Should we even consider
*replacing* the current atomicModifyIORef with that version? That could
theoretically break existing code, but I suspect it would do so very
rarely. If we don't change the existing atomicModifyIORef now, I think we
should consider deprecating it: it's very easy to accidentally use it too
lazily.