New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add atomicModifyIORef_ and atomicModifyIORef'_ to 'Data.IORef' #101
Comments
I’m in support, I ran into this odd omission in the API just last month. |
I must admit that I don't really understand the idea behind existing API. atomicModifyIORef :: IORef a -> (a -> (a, b)) -> IO b What's the expected use case when |
That's one of the most useful ones in my opinion! Imagine one has a map behind an IO ref. As one example, that function can be used to both insert into the map, and also return a bool indicating if there was a prior value in the map. That said, I think the versions already in base are slightly more useful and general, although they might sometimes require discarding the result to avoid warnings. |
Thanks @gbaz, it's a pity that haddocks do not provide such example. Would it be too bad to define |
It's been a while since I wrote it, but the code used to implement |
If you did a prior read instead, the combined block is not atomic (there could have been a write in between the two). |
I'm using the existing interface behind command <- atomicModifyIORef' watchRef popAction
popAction :: [WatchAction] -> ([WatchAction], WorkerCommand)
popAction [] = ([], Wait)
popAction (x : xs) = case x of
Start -> (xs, Greeting)
Consume l -> (xs, WriteLine l)
End path lns -> ([], Finish path lns) But that's about the only time I used this default interface. More often I need the one that's being proposed here. |
I'm confused then why one would use |
Oh, it's the modification itself that's not atomic. modifyIORef ref f = readIORef ref >>= writeIORef ref . f 🤯 |
I wonder: could we simply make |
That would have rather bad performance effects for code using an |
I made a documentation MR for the current state of |
Reviewed with suggestions. Thanks for doing that. |
Here's how you'd want to actually implement these, I believe: atomicModifyIORef_ :: IO a -> (a -> a) -> IO ()
atomicModifyIORef_ (IORef (STRef ref)) f = IO $ \s ->
case atomicModifyMutVar2# ref (MkSolo . f) s of
(# s', _old, MkSolo _new #) -> (# s', () #)
atomicModifyIORef'_ :: IO a -> (a -> a) -> IO ()
atomicModifyIORef'_ (IORef (STRef ref)) f = IO $ \s ->
case atomicModifyMutVar_# ref f s of
(# s', _old, !_new #) -> (# s', () #) But ... why throw away all the information? The following two functions should be (at least nearly) as convenient, just as efficient (assuming decent optimization), and can be used for extra things. -- | Returns the old value from the IORef and the new one installed in it.
foobar :: IO a -> (a -> a) -> IO (a, a)
foobar (IORef (STRef ref)) f = IO $ \s ->
case atomicModifyMutVar2# ref (MkSolo . f) s of
(# s', old, MkSolo new #) -> (# s', (old, new) #)
{-# INLINE foobar #-}
-- | Returns the old value from the IORef and the new one installed in it.
baz :: IO a -> (a -> a) -> IO (a, a)
baz (IORef (STRef ref)) f = IO $ \s ->
case atomicModifyMutVar_# ref f s of
(# s', old, !new #) -> (# s', (old, new) #)
{-# INLINE baz #-} Note (2023-03-25): I updated the implementations of |
So convenient I'd almost rather name the other ones the same with a |
@mixphix , I don't understand your comment. Could you be more specific about what you're comparing to what? |
It seems that the functions you've named |
Frankly I'm not a fan of any of the names in that module. I can't make heads or tails of how each one is supposed to relate to the details of what they do differently from each other. Perhaps such precise functions deserve precise (long) names? |
There's a reason I use names that are utter nonsense; coming up with good ones is hard. I believe that the fact data LSP a b = LSP !a b
-- Just force the new IORef contents, not the extracted info
bloop :: IORef a -> (a -> LSP a b) -> IO (a, LSP a b)
data SP a b = SP !a !b
-- Force everything in whichever thread gets there first
bleep :: IORef a -> (a -> SP a b) -> IO (a, SP a b) |
Let's not diverge from the original proposal.
This is the main point of contention in my opinion. The names of I'm very sympathetic to @mixphix's idea (#138 (comment)) to introduce write :: a -> IORef a -> IO ()
modify :: (a -> a) -> IORef a -> IO ()
modify' :: (a -> a) -> IORef a -> IO ()
update :: (a -> (a, b)) -> IORef a -> IO (a, (a, b))
update' :: (a -> (a, b)) -> IORef a -> IO (a, (a, b)) matching existent API of non-atomic operations in |
@Bodigrim I actually do like the idea of having a separate module I don't have the capacity to steer this new proposal, but I'm more than happy to encourage and support anyone else who'll take it 🤗 |
@Bodigrim There are reasons I'm "diverging from the proposal". I think we want a reasonably full set of the basic functionality offered by the primops. So adding less capable operations first and using up good names without discussing what to name the rest in the set seems like a bad idea. I have commented on several problems with @mixphix's suggestions in the other thread. |
AFAIU the original proposal here, it had nothing to do with a full set of primops wrappers or intricate laziness/strictness properties. @chshersh just wanted a set of atomic helpers matching existent non-atomic helpers. I mean, providing a full set of the basic primops functionality is a reasonable goal, just a different one from this proposal. |
I too like the idea of exposing the functions from a new module, making it harder to use the non-atomic ones by accident. |
I'm closing this issue as I don't want to pursue the current design anyway. |
I propose to add the following functions to the
Data.IORef
module inbase
:This functions are rather common and I personally use them much more often than the original
atomicModifyIORef
versions. Currenty, I need to writeatomicModifyIORef' ref $ \a -> (myFunction a, ())
which is:atomicModifyIORef
every single time because I always forget where is the return value: is it the first element of the tuple or the second one?This was proposed before as a GHC proposal but was rejected because it should be a CLC proposal:
A few common libraries out there already implement this function:
extra
relude
Unfortunately,
base
itself already has functions with these names but different type in theGHC.IORef
module:The text was updated successfully, but these errors were encountered: