IORef 更新后仍然引用旧值

IORef still refers to the old value after update

背景

我是Schemer,开始学习Haskell。我正在尝试在 C 中实现一个 Scheme 解释器,遵循 chapter 4 of SICP. It turns out programming directly in C is too hard. So I decide to first prototype in Haskell. With the help of Write Yourself a Scheme in 48 Hours,我已经实现了除变量、闭包和环境之外的所有内容。

问题

IORef 的修改不会在 main 的调用之间持续存在。我希望程序打印 (False) (True) (True) (True)... 但实际上它打印 (False) (True) (False) (真)(假)(真)...

代码的简化版本:

import Data.IORef

data SCM = Environment (IORef Bool) SCM | Empty'Environment

global :: IO SCM
global = Environment <$> newIORef False <*> pure Empty'Environment

print'' :: SCM -> IO ()
print'' ls =
  case ls of
    Empty'Environment -> pure ()
    Environment first rest -> readIORef first >>= putStr . show >> print'' rest

print' :: SCM -> IO ()
print' ls = putStr "(" *> print'' ls *> putStrLn ")"

main :: IO ()
main = global >>=
       \ls -> case ls of
                Empty'Environment -> pure ()
                Environment first _ -> print' ls *>
                                       modifyIORef first (const True) *>
                                       print' ls *>
                                       main

语法高亮版本:

感谢您的帮助!

我们可以将您的示例缩减为 main = (global >>= loop) >> main。问题是 global 不是单个全局变量,而是一个 IO SCM,一个将创建全局值的操作。让我们重命名它:

createSCM :: IO SCM
createSCM = Environment <$> newIORef False <*> pure Empty'Environment

现在这个名字更接近真相了。每次调用该函数时,我们都会创建一个新的 SCM。所以你的程序是这样工作的:

main = (createSCM >>= loop) >> main
     = (createSCM >>= loop) >> (createSCM >>= loop) >> main
     = (createSCM >>= loop) >> (createSCM >>= loop) >> ...

如您所见,我们一直在创建新的 SCM。因此,您没有得到预期的行为。

解决方法很简单。显式创建 globalloop

main = do
  global <- createSCM
  let loop = do
         ...
         loop
  loop