"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 a
或IO [a]
,同时需要更复杂的visitor
函数。
不过,由于我们在 IO
内部工作,因此有一个解决方法:我们可以利用 IORef
s 并收集结果。不过,我想强调的是,这不是 不是 惯用代码,人们会在 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。
我开始使用 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 a
或IO [a]
,同时需要更复杂的visitor
函数。
不过,由于我们在 IO
内部工作,因此有一个解决方法:我们可以利用 IORef
s 并收集结果。不过,我想强调的是,这不是 不是 惯用代码,人们会在 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。