2016-09-22 12 views
8

Podpis modifyIORef jest dosyć prosta:Jaki jest cel dodatkowego parametru wyniku atomicModifyIORef?

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

Niestety, to nie jest bezpieczeństwo wątków. Istnieje alternatywa, która rozwiązuje ten problem:

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

Czym dokładnie różnią się te dwie funkcje? Jak mam używać parametru b podczas modyfikowania IORef, który może być odczytany z innego wątku?

Odpowiedz

1

Jak zaznaczono w komentarzu, bez współbieżności byłbyś w stanie po prostu napisać coś jak

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

Ale w kontekście jednoczesnego, ktoś inny może zmienić odniesienie między odczytu i zapisu.

11

Dodatkowy parametr służy do podania wartości zwracanej. Na przykład możesz chcieć atomicznie zastąpić wartość zapisaną w IORef i zwrócić starą wartość. Można to zrobić tak:

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

Jeśli nie mają wartość powrotu, można użyć następujących:

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

która ma ten sam podpis jako modifyIORef.

+0

tak, gdyby został 'atomicModifyIORef :: IORef a -> (a -> a) -> IO A', wracając starą wartość, by służył ten sam cel (i być prostszy, IMO). Ciekawy. – chi

+0

Czego nie rozumiem, dlaczego potrzebuję tej funkcji dla 'atomicModifyIORef', ale nie dla' modifyIORef'? – leftaroundabout

+0

@leftaroundabout No cóż, 'modifyIORef' nie zapewnia żadnych gwarancji atomowości, więc nie byłoby to zbyt użyteczne. – redneb

2

Oto jak to rozumiem. Pomyśl o funkcjach następujących po idiomie nawiasowym, np.

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

Ta funkcja przyjmuje funkcję jako argument i zwraca zwracaną wartość tej funkcji. atomicModifyIORef jest podobny do tego. Przyjmuje funkcję jako argument, a intencją jest zwracanie wartości zwracanej przez tę funkcję. Jest tylko jedna komplikacja: funkcja argumentu ma również zwrócić nową wartość, która ma być przechowywana w IORef. Z tego powodu, atomicModifyIORef wymaga od tej funkcji, aby zwrócić dwie wartości. Oczywiście przypadek ten nie jest do końca podobny w przypadku wspornika (np. Nie ma w nim udziału, nie mamy do czynienia z bezpieczeństwem wyjątków itp.), Ale ta analogia daje pewien pomysł.

+0

Interesujące porównanie. Niemniej jednak przyjmuję [odpowiedź dfeuera] (http://stackoverflow.com/a/39682119/745903) teraz, ponieważ "dlaczego jest to potrzebne dla części' atomicModify', ale dla 'modify'" było to, o co głównie chodziło to pytanie . – leftaroundabout

1

Sposób, w jaki lubię to oglądać, to monada State. Operacja stanowa modyfikuje jakiś stan wewnętrzny i dodatkowo daje wynik. Tutaj stan znajduje się wewnątrz IORef, a wynik jest zwracany jako część operacji IO. Tak więc możemy sformułować funkcję używając State następująco:

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)