IORef仍然引用更新后的旧值

Ale*_*ong 5 io scheme haskell ioref

背景

我是一名开始学习Haskell的Schemer.我正在尝试在SICP的第4章之后在C中实现Scheme解释器.事实证明直接用C编程太难了.所以我决定先在Haskell中进行原型设计.在48小时内自己编写一个方案的帮助下,我已经实现了除变量,闭包和环境之外的所有功能.

问题

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

代码的精简版:

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
Run Code Online (Sandbox Code Playgroud)

语法高亮版本: ioref.hs

谢谢你的帮助!

Zet*_*eta 7

我们可以将你的例子减少到main = (global >>= loop) >> main.问题在于,global它不是一个单一的全局变量,而是一个IO SCM将创建全局值的动作.我们重命名一下:

createSCM :: IO SCM
createSCM = Environment <$> newIORef False <*> pure Empty'Environment
Run Code Online (Sandbox Code Playgroud)

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

main = (createSCM >>= loop) >> main
     = (createSCM >>= loop) >> (createSCM >>= loop) >> main
     = (createSCM >>= loop) >> (createSCM >>= loop) >> ...
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我们SCM一直在创造新的东西.因此,您无法获得预期的行为.

解决方案很简单.创建你的globalloop明确地:

main = do
  global <- createSCM
  let loop = do
         ...
         loop
  loop
Run Code Online (Sandbox Code Playgroud)