在IO中编写循环的更好方法

时间:2015-06-16 05:29:32

标签: loops haskell

鉴于一个png文件,我试图获取其块的列表'偏移量和尺寸。

简而言之,png files由块组成,每个块由三个4字节字段和一个可变长度字段(块的数据字段)组成。数据字段的大小存储在第一个4字节字段中(称为"长度"字段)。

因此,给定当前块的偏移量和大小(ofs,sz),可以得到下一个块的偏移量和大小(ofs',sz'),这样:

OFS' = ofs + sz

阅读sz'在offset = ofs'

给定png文件中的初始块的偏移量和大小,总是(0,8),可以循环遍历文件,直到达到它的结尾。这就是我如何做到的:

import Data.Word
import qualified Data.ByteString.Lazy as BS
import Data.Binary.Get

size :: BS.ByteString -> Int -> IO (Int)
size bytes offset = do
    let ln = runGet (do skip offset
                        l <- getWord32be
                        return l)
                    bytes
    return $ 3*4 + fromIntegral ln

offsetSizes :: Int -> BS.ByteString -> [(Int, Int)] -> IO [(Int, Int)]
offsetSizes fLen bytes oss = do
        let (offset, sz) = last oss
            offset' = offset + sz
        sz' <- size bytes offset'
        let nextOffset = offset' + sz'
        if nextOffset < fLen then offsetSizes fLen bytes $ oss ++ [(offset', sz')]
                              else return oss
main = do
    contents <- BS.readFile "myfile.png"
    let fLen = fromIntegral $ BS.length contents :: Int

    ofszs <- offsetSizes fLen contents [(0,8)]
    putStrLn $ "# of chunks: " ++ (show $ length ofszs)
    putStrLn $ "chunks [(offset,size)]: " ++ show ofszs

我的问题:我对循环并不满意。我想知道在Haskell中是否有更惯用的方法来实现相同的目标?

1 个答案:

答案 0 :(得分:1)

offsetSizes重复输入一些状态((offset, sz)对)以产生新的一对或完成。创建的所有对都被收集到一个列表中。

这个递归方案由unfoldrM包中的monad-loops捕获,允许您将offsetSizes写为

offsetSizes :: Int -> BS.ByteString -> IO [(Int, Int)]
offsetSizes fLen bytes = unfoldrM step (0, 8)
  where
    step (offset, sz) = do
        let offset' = offset + sz
        sz' <- size bytes offset'
        let state' = (offset', sz')
        return $ if offset' + sz' < fLen then Just (state', state') else Nothing