Какова цель дополнительного параметра результата atomicModifyIORef?
Подпись modifyIORef
достаточно просто:
modifyIORef :: IORef a -> (a -> a) -> IO ()
К сожалению, это не потокобезопасно. Существует альтернатива, которая решает эту проблему:
atomicModifyIORef :: IORef a -> (a -> (a,b)) -> IO b
Какие именно различия между этими двумя функциями? Как я должен использовать b
параметр при изменении IORef
что может быть прочитано из другого потока?
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)