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