Get Monad 中的 IO

5
我的问题是这样的。我正在尝试实现一个用于解析RDB文件(Redis生成的转储文件)的流式解析器。我想要实现一个类似于mapM_的函数,通过它我可以打印出每个在转储文件中表示的对象,就像解析它们一样。然而,我似乎无法使其在恒定的空间内运行。我发现正在Get单子内部构建了一个大的IO() thunk,在返回Get单子后执行IO。是否有任何方法可以在解析时将我的对象作为流打印并丢弃它们?我已经尝试过Enumerators和Conduits,但没有看到任何真正的收益。以下是我目前的代码:
loadObjs_ :: (Monad m) => (Maybe Integer -> BL8.ByteString -> RDBObj -> Get (m a)) -> Get (m a)
loadObjs_ f = do
             code <- lookAhead getWord8
             case code of
                0xfd -> do
                 skip 1
                 expire <- loadTime
                 getPairs_ f (Just expire)
               0xfc -> do
                 skip 1
                 expire <- loadTimeMs
                 getPairs_ f (Just expire)
               0xfe -> f Nothing "Switching Database" RDBNull
               0xff -> f Nothing "" RDBNull
               _ -> getPairs_ f Nothing

getPairs_ :: (Monad m) => (Maybe Integer -> BL8.ByteString -> RDBObj -> Get (m a)) -> Maybe Integer -> Get (m a)
getPairs_ f ex = do
                !t <- getWord8
                !key <- loadStringObj False
                !obj <- loadObj t
                !rest <- loadObjs_ f
                !out <- f ex key obj
                return (out >> rest)


(loadObj does the actual parsing of a single object but I believe that whatever I need to fix the streaming to operate in constant or near-constant memory is at a higher level in the iteration than loadObj)

getDBs_ :: (Monad m) => (Maybe Integer -> BL8.ByteString -> RDBObj -> Get (m a)) -> Get (m a)
getDBs_ f = do
           opc <- lookAhead getWord8
           if opc == opcodeSelectdb
              then do
                  skip 1
                  (isEncType,dbnum) <- loadLen
                  objs <- loadObjs_ f
                  rest <- getDBs_ f
                  return (objs >> rest)
              else f Nothing "EOF" RDBNull

processRDB_ :: (Monad m) => (Maybe Integer -> BL8.ByteString -> RDBObj -> Get (m a)) -> Get (m a)
processRDB_ f = do
                header <- getBytes 9
                dbs <- getDBs_ f
                eof <- getWord8
                return (dbs)

printRDBObj :: Maybe Integer -> BL8.ByteString -> RDBObj -> Get (IO ())
printRDBObj (Just exp) key obj = return $ (print ("Expires: " ++ show exp) >>
                                           print ("Key: " ++ (BL8.unpack key)) >> 
                                           print ("Obj: " ++ show obj))
printRDBObj Nothing key RDBNull = return $ (print $ BL8.unpack key)
printRDBObj Nothing key obj = return $ (print ("Key: " ++ (BL8.unpack key)) >> 
                                        print ("Obj: " ++ show obj))


main = do
       testf <- BL8.readFile "./dump.rdb"
       runGet (processRDB_ printRDBObj)  testf

提前感谢大家。

最好的祝福, Erik

编辑:这是我尝试将对象解析为懒列表,然后在懒列表上进行IO的结果。

processRDB :: Get [RDBObj]

processRDB = do
                header <- getBytes 9
                dbs <- getDBs
                eof <- getWord8
                return (dbs)

main = do
       testf <- BL8.readFile "./dump.rdb"
       mapM_ (print . show) $ runGet processRDB testf

你尝试过 http://hackage.haskell.org/package/binary-strict 吗? - Chris Kuklewicz
我还没有尝试过binary-strict,但我确实尝试了严格获取cereal,但没有成功。 - Erik Hinton
你不想让它变得更严格,反而希望让它更懒惰一些。某处对某些东西过于严格了。但我不太熟悉相关软件包的操作方法。 - Paul Johnson
@ErikHinton - 你能提供一下你的惰性列表尝试中 getDBs 的代码吗?那可能更接近你想要的。如果你从二进制转换为单子解析组合库(parsec,技术上也包括iteratee/enumerator),你也可以成功地采用这种方法。但我认为使用 Get (m a) 类型是不可能实现的。 - John L
@NathanHowell 我很想知道你是否有一种清晰的迭代解析和IO策略,可以在解析器满足条件后仅打印出转储的每个部分。 - Erik Hinton
显示剩余3条评论
1个回答

2
如果我理解你的代码正确,你正在尝试逐步将文件内容转换为IO操作,希望随后逐步执行这些操作。更好的方法是让你的解析器返回一个惰性对象列表,然后将其打印出来。

1
啊,是的,我也尝试过这个。我有一个将RDB解析为对象列表的代码版本,在其中调用mapM_ . (print . show),但不幸的是,我看到了相同的堆峰值在我的执行开始时逐渐减少,随着迭代和垃圾回收的进行: - Erik Hinton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接