atomicModifyIORef 的额外结果参数的用途是什么?

What is the purpose of the extra result parameter of atomicModifyIORef?

modifyIORef的签名很简单:

modifyIORef :: IORef a -> (a -> a) -> IO ()

不幸的是,这不是线程安全的。有一个替代方案可以解决这个问题:

atomicModifyIORef :: IORef a -> (a -> (a,b)) -> IO b

这两个函数到底有什么区别?在修改可能从另一个线程读取的 IORef 时,我应该如何使用 b 参数?

额外参数用于提供 return 值。例如,您可能希望能够自动替换存储在 IORef 中的值和 return 中的旧值。你可以这样做:

atomicModifyIORef ref (\old -> (new, old))

如果您没有 return 的值,您可以使用以下方法:

atomicModifyIORef_ :: IORef a -> (a -> a) -> IO ()
atomicModifyIORef_ ref f =
    atomicModifyIORef ref (\val -> (f val, ()))

modifyIORef具有相同的签名。

以下是我的理解。想想括号后面的函数,例如

withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r

这些函数将一个函数作为参数,return 该函数的 return 值。 atomicModifyIORef 与此类似。它以一个函数作为参数,目的是 return 该函数的 return 值。只有一个复杂的问题:参数函数还必须 return 一个新值存储在 IORef 中。因此,atomicModifyIORef 需要从该函数到 return 两个值。当然,这种情况与括号情况并不完全相似(例如不涉及IO,我们不处理异常安全等),但这个类比给你一个想法。

正如您在评论中所述,如果没有并发性,您可以编写类似

的内容
modifyAndReturn ref f = do
  old <- readIORef ref
  let !(new, r) = f old
  writeIORef r new
  return r

但是在并发上下文中,其他人可以更改读取和写入之间的引用。

我喜欢通过 State monad 来查看它。有状态操作修改一些内部状态并另外产生输出。这里的状态在 IORef 中,结果作为 IO 操作的一部分返回。所以我们可以使用 State 重新表述函数如下:

import Control.Monad.State
import Data.IORef
import Data.Tuple (swap)

-- | Applies a stateful operation to a reference and returns its result.
atomicModifyIORefState :: IORef s -> State s a -> IO a
atomicModifyIORefState ref state = atomicModifyIORef ref (swap . runState state)