Какова цель дополнительного параметра результата atomicModifyIORef?

подпись modifyIORef достаточно прост:

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

к сожалению, это не является потокобезопасным. Существует альтернатива, которая адресует эту проблему:

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

каковы именно различия между этими двумя функциями? Как я должен использовать

4 ответов


Как вы заявили в комментарии, без параллелизма вы сможете просто написать что-то вроде

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

но в параллельном контексте кто-то другой может изменить ссылку между чтением и записью.


дополнительный параметр используется для получения возвращаемого значения. Например, вы можете захотеть иметь возможность атомарно заменить значение, хранящееся в 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

эти функции принимают функцию в качестве аргумента и возвращают возвращаемое значение этой функции. atomicModifyIORef аналогично. Он принимает функцию в качестве аргумента, и намерен вернуть возвращаемое значение этой функции. Существует только одно осложнение: функция аргумента также должна возвращать новое значение для хранения в IORef. Из-за этого, atomicModifyIORef требует от эта функция возвращает два значения. Конечно, этот случай не совсем похож на случай кронштейна (например, нет IO вовлечены, мы не имеем дело с безопасностью исключений и т. д.), Но эта аналогия дает вам идею.


то, как мне нравится смотреть это через State монады. Операция с состоянием изменяет некоторое внутреннее состояние и дополнительно выдает результат. Здесь государство находится внутри 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)