8

私はData.Serialize.Get次のコンビネータを使用して定義しようとしています。

getConsumed :: Get a -> Get (ByteString, a)

これは、渡されたアクションのように動作する必要がありますが、消費されたものGetも返します。ユースケースは、解析とハッシュの両方が必要なバイナリ構造を持っていて、解析する前に長さがわからない場合です。ByteStringGet

このコンビネータは、その単純なセマンティクスにもかかわらず、実装するのが驚くほど難しいことを証明しています。

の内部を掘り下げることなくGet、私の本能はこの怪物を使用することでした:

getConsumed :: Get a -> Get (B.ByteString, a)
getConsumed g = do
  (len, r) <- lookAhead $ do
                before <- remaining
                res <- g
                after <- remaining
                return (before - after, res)
  bs <- getBytes len
  return (bs, r)

これは先読みを使用し、アクションの実行前後の残りのバイトを確認し、アクションの結果を返し、長さを消費します。これは作業を複製するべきではありませんが、次の場合に失敗することがあります。

*** Exception: GetException "Failed reading: getBytes: negative length requested\nEmpty call stack\n"

だから私はどこかで穀物について何かを誤解しているに違いありません。

誰かが私の定義の何が悪いのか見getconsumedたり、それを実装する方法についてより良いアイデアを持っていますか?

編集:Dan Doelは、特定のチャンクの残りの長さを返すことができると指摘していremainingます。これは、チャンクの境界を越える場合にはあまり役に立ちません。その場合、アクションのポイントが何であるかはわかりませんが、それが私のコードが機能しなかった理由を説明しています!今、私は実行可能な代替案を見つける必要があります。

編集2:もう少し考えてみると、ループ内の個々のチャンク()を手動でフィードし、それが何を食べているかを追跡するとremaining、現在のチャンクの長さが有利になる可能性があるようです。私がやる。私もまだこのアプローチをうまく機能させることができていませんが、元のアプローチよりも有望なようです。Getremaining >>= getBytes

編集3:興味があれば、上記の編集2のコードを次に示します。

getChunk :: Get B.ByteString
getChunk = remaining >>= getBytes

getConsumed :: Get a -> Get (B.ByteString, a)
getConsumed g = do
    (len, res) <- lookAhead $ measure g
    bs <- getBytes len
    return (bs, res)
  where
  measure :: Get a -> Get (Int ,a)
  measure g = do
    chunk <- getChunk
    measure' (B.length chunk) (runGetPartial g chunk)

  measure' :: Int -> Result a -> Get (Int, a)
  measure' !n (Fail e) = fail e
  measure' !n (Done r bs) = return (n - B.length bs, r)
  measure' !n (Partial f) = do
    chunk <- getChunk
    measure' (n + B.length chunk) (f chunk)

残念ながら、次のサンプル入力ではしばらくすると失敗するようです。

*** Exception: GetException "Failed reading: too few bytes\nFrom:\tdemandInput\n\n\nEmpty call stack\n"
4

2 に答える 2

4

シリアルパッケージには、必要なものを単に実装するのに十分な情報が格納されていません。チャンクを使用するというあなたのアイデアがうまくいくか、おそらく特別なものになると思いますrunGet。シリアルをフォークして内部を使用するのがおそらく最も簡単な方法です。

独自に作成することもできます。これは、protocol-buffersライブラリを作成するときに行ったことです。私のカスタムText.ProtocolBuffers.Getライブラリは、あなたが望むことをするのに十分な機械を実装しています:

import Text.ProtocolBuffers.Get
import Control.Applicative
import qualified Data.ByteString as B

getConsumed :: Get a -> Get (B.ByteString, a)
getConsumed thing = do
  start <- bytesRead
  (a,stop) <- lookAhead ((,) <$> thing <*> bytesRead)
  bs <- getByteString (fromIntegral (stop-start))
  return (bs,a)

私のライブラリはbyteReadの数を追跡しているので、これは明らかです。それ以外の点では、APIはCerealと非常によく似ています。

于 2012-07-11T08:22:52.947 に答える
4

編集:余分な計算を行わない別の解決策!

getConsumed :: Get a -> Get (B.ByteString, a)
getConsumed g = do
  (len, r) <- lookAhead $ do
                (res,after) <- lookAhead $ liftM2 (,) g remaining
                total <- remaining
                return (total-after, res)
  bs <- getBytes len
  return (bs, r)

lookAhead1つの解決策は、 2回呼び出すことです。1回目は、必要なすべてのチャンクがロードされていることを確認し、2回目は、実際の長さの計算を実行します(逆シリアル化されたデータを返します)。

getConsumed :: Get a -> Get (B.ByteString, a)
getConsumed g = do
  _ <- lookAhead g -- Make sure all necessary chunks are preloaded
  (len, r) <- lookAhead $ do
                before <- remaining
                res <- g
                after <- remaining
                return (before - after, res)
  bs <- getBytes len
  return (bs, r)
于 2012-07-11T11:40:33.377 に答える