使用 IO 时如何在 Haskell 中的两个函数调用之间共享 IORef 状态?

How to share IORef state between two function invocations in Haskell when using IO?

我正在努力学习 Haskell 并且我正在尝试使用 IORef 来保存和查找记录。我的 code 看起来像这样(请注意,在此示例中我选择 "String" 作为 IORef 类型只是为了方便和简洁,在我的实际代码中我使用的是记录。并且也忽略了我我正在使用 Set 而不是 Map,我会更改它):

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就可以return我刚刚插入到Set中的String。但是当我 运行 doStuff 函数时,我得到这个:

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

我可能误解了一些东西,因为我认为 IORef 确实应该是我的状态的容器。

  1. 为什么这不起作用?
  2. 我该怎么做才能让它发挥作用?

您似乎混淆了 IO IORefStateIORefState(没有 IO),更一般地说,IO aa

在你的例子中,IO IORefState的值是动作newIORef empty,代表"an action that creates a fresh new IORef from scratch"。
相比之下,IORefState(没有 IO)是正确的 原始对象,您应该在使用它的函数之间共享(saveStringToState,并且findStringInState).
然后,saveStringToStatefindStringInState分别调用newIORef empty,即各自创建一个不同的IORefState对象,不会被对方影响。

要修复,您必须在 doStuff 函数中调用 newIORef empty(作为使用 <-IO 操作)并共享 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 aa 之间的区别类似于其他编程语言中 "a function object that returns a value typed as a (with some side effects)" 和 "just a raw value typed as a" 之间的区别。