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

Haskell atomicModifyIORef的额外结果参数的用途是什么?,haskell,concurrency,ioref,global-state,Haskell,Concurrency,Ioref,Global State,modifyIORef的签名非常简单: modifyIORef :: IORef a -> (a -> a) -> IO () 不幸的是,这不是线程安全的。有一种方法可以解决这个问题: atomicModifyIORef :: IORef a -> (a -> (a,b)) -> IO b 这两种功能之间到底有什么区别?当修改可能从另一个线程读取的IORef时,如何使用b参数?额外的参数用于提供返回值。例如,您可能希望能够自动替换存储在IORef中的值并返

modifyIORef
的签名非常简单:

modifyIORef :: IORef a -> (a -> a) -> IO ()
不幸的是,这不是线程安全的。有一种方法可以解决这个问题:

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

这两种功能之间到底有什么区别?当修改可能从另一个线程读取的
IORef
时,如何使用
b
参数?

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

atomicModifyIORef ref (\old -> (new, old))
如果没有要返回的值,可以使用以下选项:

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

它的签名与
modifyIORef

相同,我是这样理解的。想想括号习惯用法后面的函数,例如

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

这些函数将函数作为参数并返回该函数的返回值<代码>原子修改ioref与此类似。它将函数作为参数,目的是返回该函数的返回值。这里只有一个复杂问题:argument函数还必须返回一个新值以存储在
IORef
中。因此,
atomicModifyIORef
需要从该函数返回两个值。当然,这种情况与括号中的情况并不完全相似(例如,不涉及
IO
,我们不涉及异常安全等),但这种类比给了您一个想法。

如您在评论中所述,如果没有并发性,您就可以编写如下内容

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

old我喜欢通过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)

因此,如果它是
atomicModifyIORef::IORef a->(a->a)->IO a
,返回旧值,则会达到相同的目的(而且更简单,IMO)。有趣的是。我不明白的是,为什么我需要这个功能用于
atomicModifyIORef
,而不是
modifyIORef
?@leftarounda好吧,
modifyIORef
无论如何都不提供任何原子性保证,所以它对它没有那么大的用处。@chi那会更简单,但目前的解决方案可以做更多的事情。例如,您可以实现某种形式的原子比较和交换,它返回一个布尔标志,指示交换是否发生。因此,如果没有并发性,我最好使用
旧的有趣的比较。尽管如此,我现在还是会接受,因为“为什么
atomicModify
需要这样做,但是
modify
需要这样做”部分就是这个问题的主要内容。