"Haskell way" 到 extract/cumulate 结果在预定义的访问者模式迭代器中

The "Haskell way" to extract/cumulate results inside an predefined vistor pattern iterator

我开始使用 Haskell(多年使用 C 和 c++)并决定尝试一个小型数据库项目。我将预定义的活页夹库用于 C 数据库库 (Database.kyotocabint)。由于使用预定义方法时效果分离,我正在努力思考如何使用迭代器接口做任何事情。

迭代数据库并将其打印出来(工作正常)的玩具演示是

test7 = do
  db <- openTree "testdatabase/mydb.kct" defaultLoggingOptions (Writer [] [])
  
  let visitor = \k v -> putStr (show k) >> putStr ":" >> putStrLn (show v) >> 
                        return (Left NoOperation)
  iterate db visitor False
  
  close db

其中 iterate 和 visitor 由库绑定提供,相关类型为

iterate :: forall db. WithDB db => db -> VisitorFull -> Writable -> IO ()
visitor :: ByteString -> ByteString -> IO (Either VisitorAction b)

但我看不出如何从迭代器内部提取信息而不是单独处理每个信息 - 例如收集列表中以 'a' 开头的所有键,甚至只是计算条目。

我是否因为迭代只有 IO () 类型而受到限制,所以我无法构建副作用并且必须重建它来替换库版本?纸上的状态 monad 似乎解决了这个问题,但访问者类型似乎不允许我在后续访问者调用中保持状态。

Haskell 解决这个问题的方法是什么?

马修

编辑 - 非常感谢下面的明确答案,其中 siad 0 不是 Haskell 方式,但也提供了解决方案 - 这个答案让我找到了 Mutable objects,我找到了一个明确的解释选项。

很遗憾,kyotocabinet 库似乎不支持您的操作。除了iterate,它应该公开一些类似的操作,returns比IO ()更复杂的东西,比如IO aIO [a],同时需要更复杂的visitor 函数。

不过,由于我们在 IO 内部工作,因此有一个解决方法:我们可以利用 IORefs 并收集结果。不过,我想强调的是,这不是 不是 惯用代码,人们会在 Haskell 中编写这种代码,但由于该库的限制,人们被迫使用某些代码。

无论如何,代码看起来像这样(未经测试):

test7 = do
  db <- openTree "testdatabase/mydb.kct" defaultLoggingOptions (Writer [] [])
  w <- newIORef []   -- create mutable var, initialize to []
  let visitor = \k v -> do
         putStrLn (show k ++ ":" ++ show v)
         modifyIORef w ((k,v):)  -- prepend (k,v) to the list w
         return (Left NoOperation)
  iterate db visitor False
  result <- readIORef w   -- get the whole list
  print result
  
  close db

由于您来自 C++,您可能希望将上面的代码与以下伪 C++ 进行比较:

std::vector<std::pair<int,int>> w;
db.iterate([&](int k, int v) {
   std::cout << k << ", " << v << "\n";
   w.push_back({k,v});
});
// here we can read w, even if db.iterate returns void

同样,这不是我认为惯用的东西 Haskell。