Haskell中的ByteStrings:我应该使用Put还是Builder?

时间:2012-07-16 20:49:12

标签: haskell functional-programming monads binary-data

我对Put monad在Builder中直接使用Data.Binary提供的内容感到困惑。我阅读了处理二进制数据的Binary Generation部分,它似乎假设您应该使用Put,但它很短并不能解释原因。

Data.Binary.Put

  

The Put monad。一个有效构造惰性字节串的monad。

type Put = PutM ()
     

仅将Lifts Builder升级为Writer monad,应用于()。

Data.Binary.Builder

  

有效构造延迟字节字符串。


适用于Writer的{​​{1}} monad有什么意义?

我可以看到()是(一个类型的同义词)monad而Put不是,但我真的不明白为什么需要Builder

在我的情况下,我正在渲染3D场景并将每个像素写为3个字节,然后将PPM格式的标题添加到开头(稍后将使用PNG)。

Put似乎意味着要为可以对二进制数据进行序列化和反序列化的类型进行实例化。这不是我正在做的事情,但为我的颜色类型实例化Binary感觉很自然

Binary

这使得instance (Binary a) => Binary (Colour a) where put (Colour r g b) = put r >> put g >> put b get = Colour <$> get <*> get <*> get put变为24位变得容易。但是后来我也必须加上标题,我不知道该怎么做。

Colour Word8是否隐藏在幕后,还是依赖于? Builder类仅用于(反)序列化数据,还是用于所有二进制生成目的?

3 个答案:

答案 0 :(得分:10)

  

我可以看到Put是monad而Builder不是,但我真的不明白为什么需要Put

准确地说,PutMMonad。这是为了方便起见,并为您提供更少的错误机会。以monadic或applicative样式编写代码通常比明确地携带所有临时代码更方便,并且在Monad实例中完成管道,你不能在中间意外使用错误的Builder你的功能。

您只需PutM即可使用Builder执行所有操作,但通常编写代码的工作量更大。

  

但接下来我还要抓住标题,我不知道该怎么办。

我不知道PPM格式,所以我不知道如何构造标题。但是在构建它之后,您只需使用putByteStringputLazyByteString来解决它。

答案 1 :(得分:10)

首先要注意概念上的差异。构建器用于高效构建字节串流,而PutM monad实际上用于序列化。因此,您应该问自己的第一个问题是,您是否实际上是在序列化(回答问自己是否存在有意义且完全相反的操作 - 反序列化)。

一般情况下,我会选择Builder以方便它提供。但是,不是二进制包中的Builder,而是来自 blaze-builder 包。它是一个monoid并且有许多预定义的字符串生成器。它也非常易于组合。最后它非常快,实际上可以进行微调。

最后但并非最不重要的是,如果您真的想要速度,便利和优雅的代码,您需要将其与各种流处理器库中的一个结合起来,例如 conduit 枚举器管道

答案 2 :(得分:3)

我不确定这在多大程度上是准确的,但我的理解一直是Put的呈现,如你所见,主要是滥用记号,这样你就可以编写这样的代码:

putThing :: Thing -> Put
putThing (Thing thing1 thing2) = do
  putThing1 thing1
  putThing2 thing2

我们没有使用Monad的“本质”(特别是,我们从不绑定任何结果)但我们获得了连接的方便和干净的语法。然而,相比纯粹的幺半选择的美学优势:

putThing :: Thing -> Builder
putThing (Thing thing1 thing2) = mconcat [
  putThing thing1,
  putThing thing2]
在我看来,

相当小。

(请注意,相比之下,Get真的 是Monad,并且可以通过明确的方式获益。