Как разделить состояние IORef между двумя вызовами функций в Haskell при использовании IO?

Я пытаюсь изучить Haskell, и я играю с IORef, к которому я пытаюсь сохранять и находить записи. Мой код выглядит примерно так (обратите внимание, что я выбрал "String" в качестве типа IORef в этом примере только для удобства и краткости, в моем реальном коде я использую запись. И также игнорирую, что вместо этого я использую Set карты, я это изменю)

module MyTest where

import           Data.IORef
import           Data.Set
import           Data.Foldable          (find)

type State = (Set String)
type IORefState = IORef State

saveStringToState :: IO IORefState -> String -> IO String
saveStringToState stateIO string = do
  state <- stateIO
  atomicModifyIORef
    state
    (\oldStrings ->
       let updatedStrings = insert string oldStrings
       in (updatedStrings, updatedStrings))
  stringsState <- readIORef state :: IO State
  putStrLn ("### saved: " ++ show stringsState)
  return string

findStringInState :: IO IORefState -> String -> IO (Maybe String)
findStringInState stateIO soughtString = do
  state <- stateIO :: IO IORefState
  strings <- readIORef state :: IO State
  putStrLn ("Looking for " ++ soughtString ++ " in: " ++ show strings)
  return $ find (== soughtString) strings

doStuff =
  let stateIO = newIORef empty
  in do saveStringToState stateIO "string1"
        findStringInState stateIO "string1"

Чего я хочу добиться, так это разделить состояние (Set) между двумя вызовами функций, чтобы findStringInState может вернуть String что я только что вставил в набор. Но когда я бегу doStuff Функция я получаю это:

*MyTest> doStuff
### saved: fromList ["string1"]
Looking for string1 in: fromList []
Nothing

Возможно, я что-то неправильно понял, так как думал, что IORef действительно должен быть контейнером для моего состояния.

  1. Почему это не работает?
  2. Что я могу сделать, чтобы это работало?

1 ответ

Решение

Кажется что ты запутался IO IORefState с IORefState (без IO), в более общем смысле, IO a с a,

В вашем случае значение IO IORefState это действие newIORef empty, которое представляет собой "действие, которое создает свежий новый IORef с нуля".
В отличие от IORefState (без IO) является правильным, необработанным объектом, который вы должны разделить между функциями, которые его используют (saveStringToState, а также findStringInState).
Тогда оба saveStringToState а также findStringInState отдельно звонить newIORef emptyт.е. каждый из них создает разные IORefState объект, который не может быть затронут другим.

Чтобы исправить, вы должны позвонить newIORef empty (как IO действие с использованием <-) в doStuff функционировать и делиться IORefState создано newIORef empty вместо IO IORefState:

  saveStringToState :: IORefState -> String -> IO String
  ...

  findStringInState :: IORefState -> String -> IO (Maybe String)
  ...

  let stateIO = newIORef empty
  in do ioRef <- stateIO
        saveStringToState ioRef "string1"
        findStringInState ioRef "string1"
  -- Or, more simply:
  do ioRef <- newIORef empty
     saveStringToState ioRef "string1"
     findStringInState ioRef "string1"

На мой взгляд, разница между IO a с a похоже на разницу между "функциональным объектом, который возвращает значение, набранное как a (с некоторыми побочными эффектами)"и" просто необработанное значение, напечатанное как a"на других языках программирования.

Другие вопросы по тегам