Какова цель дополнительного параметра результата 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)