2016-09-22 2 views
8

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

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

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

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

В чем конкретно отличия между этими двумя функциями? Как я должен использовать параметр b при изменении IORef, который может быть прочитан из другого потока?

ответ

1

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

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

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

11

Дополнительный параметр используется для предоставления возвращаемого значения. Например, вы можете захотеть атомарно заменить значение, хранящееся в IORef, и вернуть старое значение. Вы можете сделать это следующим образом:

atomicModifyIORef ref (\old -> (new, old)) 

Если вы не имеете значения для возврата, вы можете использовать следующее:

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

, которая имеет ту же сигнатуру, modifyIORef.

+0

Таким образом, если бы он был 'atomicModifyIORef :: IORef а -> (а -> а) -> IO a', возвращая старое значение, служили бы та же цель (и быть проще, ИМО). Интересно. – chi

+0

Я не понимаю, зачем мне эту функцию для 'atomicModifyIORef', но не для' modifyIORef'? – leftaroundabout

+0

@leftaroundabout Ну, 'modifyIORef' в любом случае не предоставляет никаких свойств атомарности, поэтому для этого это было бы не так полезно. – redneb

2

Вот как я это понимаю. Подумайте о функциях, которые следуют за иконой скобки, например.

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

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

+0

Интересное сравнение. Тем не менее, я все же соглашусь с ответом [dfeuer] (http://stackoverflow.com/a/39682119/745903), поскольку «почему это необходимо для« atomicModify », но для части« modify »было то, что этот вопрос в основном касался , – leftaroundabout

1

Способ, которым я хотел бы посмотреть это через монадию 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)