垃圾收集列表,同时 运行 对其进行 IO 操作

Garbage collecting a list while running an IO action over it

我想在 Haskell 中编写一个共轭梯度求解器,并想使用惰性列表将停止规则和信息输出从迭代中分离出来。我的代码基本上是这样的:

data CGState = CGState { cgx :: Image
                       , cgp :: Image
                       , cgr :: Image
                       , cgr2 :: Double
                       }

cg :: Operator -> Image -> [CGState]
cg = [...]

runCG :: (CGState -> Bool) -> Operator -> Image -> IO Image
runCG stoprule op rhs = do
  let steps = takeWhile (not . stoprule) $ cg op rhs
  fmap last $ forM (zip [(1::Int)..] steps) $ \(n, cgs) -> do
      putStrLn $ show n ++ "  " ++ show (sqrt $ cgr2 cgs)
      return $ cgx cgs

思路是遍历列表,输出一些信息,但只保留最后一次迭代。但是,当 运行 这段代码时,它似乎不会对前面的迭代进行垃圾回收。我的猜测是这与 IO 相关:如果我重写代码

runCG :: (CGState -> Bool) -> Operator -> Image -> IO Image
runCG stoprule op rhs = do
  let steps = takeWhile (not . stoprule) $ cg op rhs
  return $ cgx $ last steps

问题没有发生,即除了最终迭代之外的所有内容都直接被垃圾收集。

如何在能够输出有关迭代的一些信息的同时达到相同的效果?

是的,我认为问题出在 IO 中的 fmap:因为 IO 操作总是按严格顺序执行,所以 fmap 只应用 last 之后 forM 的整个结果列表已经构建。

相反,您可能可以使用 Control.Monad.foldM,它在列表上单次折叠(未经测试):

runCG stoprule op rhs = do
  let steps = takeWhile (not . stoprule) $ cg op rhs
  foldM (\ _ (n, cgs) -> do
      putStrLn $ show n ++ "  " ++ show (sqrt $ cgr2 cgs)
      return $ cgx cgs)
    undefined -- initial value for fold is ignored as long as steps is nonempty
    (zip [(1::Int)..] steps)