Discussion:
Discussion: New atomic IORef functions
David Feuer
2018-07-07 18:35:24 UTC
Permalink
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.
David Feuer
2018-07-07 19:09:33 UTC
Permalink
Whoops! I left out the proposal link:

https://github.com/ghc-proposals/ghc-proposals/pull/149

Also, what I called atomicModifyIORef_ below should really be called
something like atomicModifyIORef'_, since it forces a polymorphic value.

Another thing to note: the underlying atomicModifyMutVar2# primop actually
supports more than just pairs. It can handle triples, solos, and any other
record types whose first components are lifted:

atomicModifyIORefSoloLazy
:: IORef a -> (a -> Solo a) -> IO (Solo a)

atomicModifyIORefSolo
:: IORef a -> (a -> Solo a) -> IO a

atomicModifyIORef3, atomicModifyIORef3Lazy
:: IORef a -> (a -> (a, b, c)) -> IO (a, b, c)

etc.

Should we add any such?
Post by David Feuer
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.
-- 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
atomicModifyIORef2
:: IORef a -> (a -> (a, b)) -> IO (a, (a, b))
atomicModifyIORef2 ref f = do
return r
atomicModifyIORef_
:: IORef a -> (a -> a) -> IO (a, a)
atomicModifyIORef_ ref f = do
return r
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.
winter
2018-07-08 07:36:06 UTC
Permalink
I believe new variations should always be motivated by use-case if
there're too many choices, the lazy behavior of old `atomicModifyIORef`
is justified by some cases the modifying functions are lazy in its
argument, thus a lazy version could win by not forcing previous thunks,
we'd want to keep its behavior as how it's documented.

As for tuples more than pairs, they're not really needed, user can
always squeeze their product into `b` component.

IMHO only the addition of `atomicModifyIORef_` is sensible in the
context of base, other APIs may go to package like primitives. But if
you have a motivated use case with `atomicModifyIORef2`, etc. Please
tell me.
Post by David Feuer
https://github.com/ghc-proposals/ghc-proposals/pull/149
Also, what I called atomicModifyIORef_ below should really be called
something like atomicModifyIORef'_, since it forces a polymorphic value.
Another thing to note: the underlying atomicModifyMutVar2# primop
actually supports more than just pairs. It can handle triples, solos,
atomicModifyIORefSoloLazy
  :: IORef a -> (a -> Solo a) -> IO (Solo a)
atomicModifyIORefSolo
  :: IORef a -> (a -> Solo a) -> IO a
atomicModifyIORef3, atomicModifyIORef3Lazy
  :: IORef a -> (a -> (a, b, c)) -> IO (a, b, c)
etc.
Should we add any such?
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.
-- 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
atomicModifyIORef2
  :: IORef a -> (a -> (a, b)) -> IO (a, (a, b))
atomicModifyIORef2 ref f = do
  return r
atomicModifyIORef_
  :: IORef a -> (a -> a) -> IO (a, a)
atomicModifyIORef_ ref f = do
  return r
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.
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
David Feuer
2018-07-11 16:51:00 UTC
Permalink
All the fundamental functions I've defined can be usefully used in the base
library. I think that's one good reason to put them there. Another is that
while the basic array operations have traditionally been exposed through
array, vector, and primitive, and the basic TVar operations have been
exposed through stm, the basic IORef and MVar operations have been exposed
through base (except, for some reason, casMutVar#). I see no reason to
change that.

I never proposed a function that forces the previous value unnecessarily,
so I don't know why you're complaining about that. The extra laziness I
don't like is in the pair result; none of the uses I've seen thus can far
make intentional use of that. That's why I tend to think atomicModifyIORef
(as it exists today) is almost never what people actually want.

Squeezing into a second component leads to extra allocation in what may be
a performance-critical function; that said, I'm willing to hold off on
higher tuples for now.
Post by winter
I believe new variations should always be motivated by use-case if
there're too many choices, the lazy behavior of old `atomicModifyIORef` is
justified by some cases the modifying functions are lazy in its argument,
thus a lazy version could win by not forcing previous thunks, we'd want to
keep its behavior as how it's documented.
As for tuples more than pairs, they're not really needed, user can always
squeeze their product into `b` component.
IMHO only the addition of `atomicModifyIORef_` is sensible in the context
of base, other APIs may go to package like primitives. But if you have a
motivated use case with `atomicModifyIORef2`, etc. Please tell me.
https://github.com/ghc-proposals/ghc-proposals/pull/149
Also, what I called atomicModifyIORef_ below should really be called
something like atomicModifyIORef'_, since it forces a polymorphic value.
Another thing to note: the underlying atomicModifyMutVar2# primop actually
supports more than just pairs. It can handle triples, solos, and any other
atomicModifyIORefSoloLazy
:: IORef a -> (a -> Solo a) -> IO (Solo a)
atomicModifyIORefSolo
:: IORef a -> (a -> Solo a) -> IO a
atomicModifyIORef3, atomicModifyIORef3Lazy
:: IORef a -> (a -> (a, b, c)) -> IO (a, b, c)
etc.
Should we add any such?
Post by David Feuer
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.
-- 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
atomicModifyIORef2
:: IORef a -> (a -> (a, b)) -> IO (a, (a, b))
atomicModifyIORef2 ref f = do
return r
atomicModifyIORef_
:: IORef a -> (a -> a) -> IO (a, a)
atomicModifyIORef_ ref f = do
return r
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.
_______________________________________________
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Dr.Koster
2018-07-11 18:22:05 UTC
Permalink
But if you force the pair result, you have to evaluate modifying function isn't it? That's sometime unwanted when you have an very expensive f here, let's say a deep binary search which might not depend previous result.


I think that is also why current atomicModifyIORef is designed this way, so I'd rather keep it the old way.


·¢×ÔÎÒµÄiPhone

------------------ Original ------------------
From: David Feuer <***@gmail.com>
Date: Thu,Jul 12,2018 0:51 AM
To: winter <***@qq.com>
Cc: Haskell Libraries <***@haskell.org>
Subject: Re: Discussion: New atomic IORef functions



All the fundamental functions I've defined can be usefully used in the base library. I think that's one good reason to put them there. Another is that while the basic array operations have traditionally been exposed through array, vector, and primitive, and the basic TVar operations have been exposed through stm, the basic IORef and MVar operations have been exposed through base (except, for some reason, casMutVar#). I see no reason to change that.


I never proposed a function that forces the previous value unnecessarily, so I don't know why you're complaining about that. The extra laziness I don't like is in the pair result; none of the uses I've seen thus can far make intentional use of that. That's why I tend to think atomicModifyIORef (as it exists today) is almost never what people actually want.


Squeezing into a second component leads to extra allocation in what may be a performance-critical function; that said, I'm willing to hold off on higher tuples for now.

On Sun, Jul 8, 2018, 3:36 AM winter <***@qq.com> wrote:


I believe new variations should always be motivated by use-case if there're too many choices, the lazy behavior of old `atomicModifyIORef` is justified by some cases the modifying functions are lazy in its argument, thus a lazy version could win by not forcing previous thunks, we'd want to keep its behavior as how it's documented.


As for tuples more than pairs, they're not really needed, user can always squeeze their product into `b` component.


IMHO only the addition of `atomicModifyIORef_` is sensible in the context of base, other APIs may go to package like primitives. But if you have a motivated use case with `atomicModifyIORef2`, etc. Please tell me.

On 2018Äê07ÔÂ08ÈÕ 03:09, David Feuer wrote:

Whoops! I left out the proposal link:

https://github.com/ghc-proposals/ghc-proposals/pull/149



Also, what I called atomicModifyIORef_ below should really be called something like atomicModifyIORef'_, since it forces a polymorphic value.


Another thing to note: the underlying atomicModifyMutVar2# primop actually supports more than just pairs. It can handle triples, solos, and any other record types whose first components are lifted:


atomicModifyIORefSoloLazy
:: IORef a -> (a -> Solo a) -> IO (Solo a)


atomicModifyIORefSolo
:: IORef a -> (a -> Solo a) -> IO a


atomicModifyIORef3, atomicModifyIORef3Lazy
:: IORef a -> (a -> (a, b, c)) -> IO (a, b, c)


etc.


Should we add any such?




On Sat, Jul 7, 2018, 2:35 PM David Feuer <***@gmail.com> wrote:

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.






_______________________________________________ Libraries mailing list ***@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
***@haskell.org
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
David Feuer
2018-07-11 18:31:00 UTC
Permalink
I'm not committed to changing atomicModifyIORef. I'm much more
interested in adding atomicModifyIORef2, atomicModifyIORef'_, and
atomicSwapIORef. I do think it would be helpful to get a pair-strict
version of atomicModifyIORef (atomicModifyIORefP?), but I guess it's
not horrible if users have to write their own with atomicModifyIORef2.
The lazy atomicModifyIORef2Lazy and atomicModifyIORefLazy_ are pretty
optional: I see their laziness as more an implementation detail than
an essential feature.
Post by Dr.Koster
But if you force the pair result, you have to evaluate modifying function
isn't it? That's sometime unwanted when you have an very expensive f here,
let's say a deep binary search which might not depend previous result.
I think that is also why current atomicModifyIORef is designed this way, so
I'd rather keep it the old way.
发自我的iPhone
------------------ Original ------------------
Date: Thu,Jul 12,2018 0:51 AM
Subject: Re: Discussion: New atomic IORef functions
All the fundamental functions I've defined can be usefully used in the base
library. I think that's one good reason to put them there. Another is that
while the basic array operations have traditionally been exposed through
array, vector, and primitive, and the basic TVar operations have been
exposed through stm, the basic IORef and MVar operations have been exposed
through base (except, for some reason, casMutVar#). I see no reason to
change that.
I never proposed a function that forces the previous value unnecessarily, so
I don't know why you're complaining about that. The extra laziness I don't
like is in the pair result; none of the uses I've seen thus can far make
intentional use of that. That's why I tend to think atomicModifyIORef (as it
exists today) is almost never what people actually want.
Squeezing into a second component leads to extra allocation in what may be a
performance-critical function; that said, I'm willing to hold off on higher
tuples for now.
Post by winter
I believe new variations should always be motivated by use-case if
there're too many choices, the lazy behavior of old `atomicModifyIORef` is
justified by some cases the modifying functions are lazy in its argument,
thus a lazy version could win by not forcing previous thunks, we'd want to
keep its behavior as how it's documented.
As for tuples more than pairs, they're not really needed, user can always
squeeze their product into `b` component.
IMHO only the addition of `atomicModifyIORef_` is sensible in the context
of base, other APIs may go to package like primitives. But if you have a
motivated use case with `atomicModifyIORef2`, etc. Please tell me.
https://github.com/ghc-proposals/ghc-proposals/pull/149
Also, what I called atomicModifyIORef_ below should really be called
something like atomicModifyIORef'_, since it forces a polymorphic value.
Another thing to note: the underlying atomicModifyMutVar2# primop actually
supports more than just pairs. It can handle triples, solos, and any other
atomicModifyIORefSoloLazy
:: IORef a -> (a -> Solo a) -> IO (Solo a)
atomicModifyIORefSolo
:: IORef a -> (a -> Solo a) -> IO a
atomicModifyIORef3, atomicModifyIORef3Lazy
:: IORef a -> (a -> (a, b, c)) -> IO (a, b, c)
etc.
Should we add any such?
Post by David Feuer
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.
-- 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
atomicModifyIORef2
:: IORef a -> (a -> (a, b)) -> IO (a, (a, b))
atomicModifyIORef2 ref f = do
return r
atomicModifyIORef_
:: IORef a -> (a -> a) -> IO (a, a)
atomicModifyIORef_ ref f = do
return r
The classic atomicModifyIORef also admits a less gratuitously lazy
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.
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
winter
2018-07-12 03:32:11 UTC
Permalink
OK then, here's some subtle things i want to shout out if you're going to make the change ; )

1. The proposed naming is just not great, how about these:

atomicExchangeIORef :: IORef a -> (a -> (a, b)) -> IO (a, (a, b))
atomicExchangeIORef' :: IORef a -> (a -> (a, b)) -> IO (a, (a, b))

atomicApplyIORef :: IORef a -> (a -> a) -> IO (a, a)
atomicApplyIORef :: IORef a -> (a -> a) -> IO (a, a)

It may not that great but it's more informative IMO.

3. atomicExchangeIORef should not force the tuple, e.g. don't pattern match on the tuple result, instead let users choose to force or not. (May worth adding some document, I'd be happy to help)

4. atomicExchangeIORef' will not only force the tuple, but also force both `a` and `b` part to match atomicModifyIORef' 's semantics.

5. atomicApplyIORef(and atomicApplyIORef') deserve its own primop, since we can skip building selector thunks all together.
Post by David Feuer
I'm not committed to changing atomicModifyIORef. I'm much more
interested in adding atomicModifyIORef2, atomicModifyIORef'_, and
atomicSwapIORef. I do think it would be helpful to get a pair-strict
version of atomicModifyIORef (atomicModifyIORefP?), but I guess it's
not horrible if users have to write their own with atomicModifyIORef2.
The lazy atomicModifyIORef2Lazy and atomicModifyIORefLazy_ are pretty
optional: I see their laziness as more an implementation detail than
an essential feature.
Post by Dr.Koster
But if you force the pair result, you have to evaluate modifying function
isn't it? That's sometime unwanted when you have an very expensive f here,
let's say a deep binary search which might not depend previous result.
I think that is also why current atomicModifyIORef is designed this way, so
I'd rather keep it the old way.
发自我的iPhone
------------------ Original ------------------
Date: Thu,Jul 12,2018 0:51 AM
Subject: Re: Discussion: New atomic IORef functions
All the fundamental functions I've defined can be usefully used in the base
library. I think that's one good reason to put them there. Another is that
while the basic array operations have traditionally been exposed through
array, vector, and primitive, and the basic TVar operations have been
exposed through stm, the basic IORef and MVar operations have been exposed
through base (except, for some reason, casMutVar#). I see no reason to
change that.
I never proposed a function that forces the previous value unnecessarily, so
I don't know why you're complaining about that. The extra laziness I don't
like is in the pair result; none of the uses I've seen thus can far make
intentional use of that. That's why I tend to think atomicModifyIORef (as it
exists today) is almost never what people actually want.
Squeezing into a second component leads to extra allocation in what may be a
performance-critical function; that said, I'm willing to hold off on higher
tuples for now.
Post by winter
I believe new variations should always be motivated by use-case if
there're too many choices, the lazy behavior of old `atomicModifyIORef` is
justified by some cases the modifying functions are lazy in its argument,
thus a lazy version could win by not forcing previous thunks, we'd want to
keep its behavior as how it's documented.
As for tuples more than pairs, they're not really needed, user can always
squeeze their product into `b` component.
IMHO only the addition of `atomicModifyIORef_` is sensible in the context
of base, other APIs may go to package like primitives. But if you have a
motivated use case with `atomicModifyIORef2`, etc. Please tell me.
https://github.com/ghc-proposals/ghc-proposals/pull/149
Also, what I called atomicModifyIORef_ below should really be called
something like atomicModifyIORef'_, since it forces a polymorphic value.
Another thing to note: the underlying atomicModifyMutVar2# primop actually
supports more than just pairs. It can handle triples, solos, and any other
atomicModifyIORefSoloLazy
:: IORef a -> (a -> Solo a) -> IO (Solo a)
atomicModifyIORefSolo
:: IORef a -> (a -> Solo a) -> IO a
atomicModifyIORef3, atomicModifyIORef3Lazy
:: IORef a -> (a -> (a, b, c)) -> IO (a, b, c)
etc.
Should we add any such?
Post by David Feuer
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.
-- 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
atomicModifyIORef2
:: IORef a -> (a -> (a, b)) -> IO (a, (a, b))
atomicModifyIORef2 ref f = do
return r
atomicModifyIORef_
:: IORef a -> (a -> a) -> IO (a, a)
atomicModifyIORef_ ref f = do
return r
The classic atomicModifyIORef also admits a less gratuitously lazy
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.
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
David Feuer
2018-07-12 05:07:53 UTC
Permalink
Post by winter
OK then, here's some subtle things i want to shout out if you're going to
make the change ; )
atomicExchangeIORef :: IORef a -> (a -> (a, b)) -> IO (a, (a, b))
atomicExchangeIORef' :: IORef a -> (a -> (a, b)) -> IO (a, (a, b))
atomicApplyIORef :: IORef a -> (a -> a) -> IO (a, a)
atomicApplyIORef :: IORef a -> (a -> a) -> IO (a, a)
It may not that great but it's more informative IMO.
I agree that my names aren't great, but I think yours are worse.
Post by winter
3. atomicExchangeIORef should not force the tuple, e.g. don't pattern
match on the tuple result, instead let users choose to force or not. (May
worth adding some document, I'd be happy to help)
I generally disagree. Being lazy in the tuple is pretty much an invitation
to memory leaks. I think the lazy version should ideally also be available
(it's what the new primop will provide, after all), but I don't think it
should be the obvious thing for people to reach for.
Post by winter
4. atomicExchangeIORef' will not only force the tuple, but also force both
`a` and `b` part to match atomicModifyIORef' 's semantics.
I don't see the need for this. Users can easily force exactly what they
want using atomicModifyIORef2 (or if they really want,
atomicModifyIORef2Lazy).
Post by winter
5. atomicApplyIORef(and atomicApplyIORef') deserve its own primop, since
we can skip building selector thunks all together.
Yes, I've implemented that primop already, and it will be added. I'm still
looking for an expert to help implement the simplest of the lot,
atomicSwapMutVar#, as efficiently as possible.
winter
2018-07-12 06:56:13 UTC
Permalink
Post by David Feuer
I generally disagree. Being lazy in the tuple is pretty much an
invitation to memory leaks. I think the lazy version should ideally also
be available (it's what the new primop will provide, after all), but I
don't think it should be the obvious thing for people to reach for.

let's put lazy modifying cases aside(which is not that rare), I'm pretty
sure haskell forks will use strict version from time to time, it's not
the lack of ' make a funtion more easy to reach, it's really just a
matter of document,  why breaking consistency with original
atomicModifyIORef/atomicModifyIORef'  ? I expect the old mnemonic still
works on new ones.
Post by David Feuer
I don't see the need for this. Users can easily force exactly what
they want using atomicModifyIORef2 (or if they really want,
atomicModifyIORef2Lazy).

Exactly the same reason with above, if people have to force the a and b
part to stop leakage, then this strict version is not really doing a
good job IMHO, I just don't see the value that you have already forced
the modification function, but then stopped at a half place where left a
thunk inside the IORef.
Post by David Feuer
OK then, here's some subtle things i want to shout out if you're
going to make the change ; )
atomicExchangeIORef :: IORef a -> (a -> (a, b)) -> IO (a, (a, b))
atomicExchangeIORef' :: IORef a -> (a -> (a, b)) -> IO (a, (a, b))
atomicApplyIORef :: IORef a -> (a -> a) -> IO (a, a)
atomicApplyIORef :: IORef a -> (a -> a) -> IO (a, a)
It may not that great but it's more informative IMO.
I agree that my names aren't great, but I think yours are worse.
3. atomicExchangeIORef should not force the tuple, e.g. don't
pattern match on the tuple result, instead let users choose to
force or not. (May worth adding some document, I'd be happy to help)
I generally disagree. Being lazy in the tuple is pretty much an
invitation to memory leaks. I think the lazy version should ideally
also be available (it's what the new primop will provide, after all),
but I don't think it should be the obvious thing for people to reach for.
4. atomicExchangeIORef' will not only force the tuple, but also
force both `a` and `b` part to match atomicModifyIORef' 's semantics.
I don't see the need for this. Users can easily force exactly what
they want using atomicModifyIORef2 (or if they really want,
atomicModifyIORef2Lazy).
5. atomicApplyIORef(and atomicApplyIORef') deserve its own primop,
since we can skip building selector thunks all together.
Yes, I've implemented that primop already, and it will be added. I'm
still looking for an expert to help implement the simplest of the lot,
atomicSwapMutVar#, as efficiently as possible.
Loading...