我想在Haskell中编写一个结构化的二进制文件。 例如,假设前四个字节应为“TEST”(以ASCII为单位),后跟数字1,2,3,4,然后是32个字节,每个字节值为128,然后是Little Endian格式的数字2048。 / p>
这意味着,创建的文件(如十六进制)应如下所示:
54 45 53 54 01 02 03 04 80 80 [... 30字节以上...] 00 08
所以基本上我有一个自定义数据结构,比如说
data MyData = MyData {
header :: String -- "TEST"
n1 :: Integer -- 1
n2 :: Integer -- 2
n3 :: Integer -- 3
block :: [Integer] -- 32 times 128
offset :: Integer -- 2048
}
现在我想将此数据写入文件。所以基本上我需要将这个结构转换成一个长ByteString。我找不到干净的惯用方法来做到这一点。理想情况下我有一个功能
MyDataToByteString :: MyData -> ByteString
或
MyDataToPut :: MyData -> Put
但我无法找到如何创建这样的功能。
背景资料:我想以脉冲跟踪器格式(http://schismtracker.org/wiki/ITTECH.TXT)编写一首歌,这是分裂跟踪器软件的二进制格式。
更新1
对于endian转换,我想我可以按如下方式提取单个字节:
getByte :: Int -> Int -> Int
getByte b num = shift (num .&. bitMask b) (8-8*b)
where bitMask b = sum $ map (2^) [8*b-8 .. 8*b-1]
答案 0 :(得分:2)
你可以尝试这个(恐怕不是最佳的):
import qualified Data.List as L
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString as B
data MyData = MyData {
header :: String -- "TEST"
, n1 :: Integer -- 1
, n2 :: Integer -- 2
, n3 :: Integer -- 3
, n4 :: Integer -- 4
, block :: [Integer] -- 32 times 128
, offset :: [Integer] -- 2048 in little endian
} deriving (Show)
d = MyData "TEST" 1 2 3 4 (L.replicate 32 128) [0, 8]
myDataToByteString :: MyData -> B.ByteString
myDataToByteString (MyData h n1 n2 n3 n4 b o) =
B.concat [
BC.pack h
, B.pack (map fromIntegral [n1, n2, n3, n4])
, B.pack (map fromIntegral b)
, B.pack (map fromIntegral o)
]
答案 1 :(得分:2)
binary
或cereal
包提供了数据结构的简单ByteString
序列化。以下是使用binary
{-# LANGUAGE DeriveGeneric #-}
import Data.Binary
import qualified Data.ByteString.Char8 as B8
import Data.Word8
import GHC.Generics
data MyData = MyData
{ header :: B8.ByteString
, n1 :: Int
, n2 :: Int
, n3 :: Int
, n4 :: Int
, block :: [Word8]
, offset :: Int
} deriving (Generic, Show)
instance Binary MyData
myData = MyData (B8.pack "Test") 1 2 3 4 (replicate 32 128) 2048
注意我已将block
的类型更改为[Word8]
,因为问题表明这些是字节。
我们为GHC.Generics
派生MyData
个实例,允许binary
包自动生成Binary
个实例。现在,我们可以使用ByteString
将数据序列化为encode
,并使用decode
反序列化:
λ. let encoded = encode myData
λ. :t encoded
encoded :: Data.ByteString.Lazy.Internal.ByteString
λ. let decoded = decode encoded :: MyData
λ. decoded
MyData {header = "Test", n1 = 1, n2 = 2, n3 = 3, n4 = 4, block = [128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128], offset = 2048}