私はData.Serialize.Get
次のコンビネータを使用して定義しようとしています。
getConsumed :: Get a -> Get (ByteString, a)
これは、渡されたアクションのように動作する必要がありますが、消費されたものGet
も返します。ユースケースは、解析とハッシュの両方が必要なバイナリ構造を持っていて、解析する前に長さがわからない場合です。ByteString
Get
このコンビネータは、その単純なセマンティクスにもかかわらず、実装するのが驚くほど難しいことを証明しています。
の内部を掘り下げることなく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
、現在のチャンクの長さが有利になる可能性があるようです。私がやる。私もまだこのアプローチをうまく機能させることができていませんが、元のアプローチよりも有望なようです。Get
remaining >>= 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"