2013-11-02 2 views
2

나는 간단한 바이너리 인코딩으로 놀아 왔으며, 대부분 모나드를 추가하기 전까지는 제대로 작동하는 것처럼 보였다. 현재 상태를 사용하여 내가 지금까지 작성한 것에 대한 룩업 테이블을 유지하고 문자열의 이전 인스턴스에 오프셋을 복사하여 복제하지 않고 작성합니다.하스켈 스테이트 모나드와 바이너리가 모든 것을 출력하지 않는다.

나는 체크하고 실행하는 모든 타입을 얻었지만, 체인에서 최종 명령을 쓰는 것만 알았다. 나는 Control.Monad.State.Strict를 사용하는 것으로 바꾸었지만 아무런 차이가 없었으므로 다른 곳에서는 근본적인 오류를 범하고있는 것으로 판단된다. 그러나 나는 어디에서 코드를 기본 기능으로 줄 였는지 모른다. . 또한, 이것을하는 더 관용적 인 방법이 있습니까?

{-# LANGUAGE OverloadedStrings #-} 


import   Control.Applicative 
import qualified Control.Monad.State.Strict as S 
import   Data.Binary.Put 
import qualified Data.ByteString   as BS 
import qualified Data.ByteString.Lazy  as BL 

data SState = SState { 
    wsPosition :: Int 
    -- plus whatever else 
} 

initialState = SState 0 
type StatePut = S.State SState Put 

class StateBinary a where 
    sput :: a -> StatePut 

incPos :: Int -> S.State SState() 
incPos amnt = do 
    (SState p) <- S.get 
    S.put $ SState (p + amnt) 

writeSized :: Int -> (a -> Put) -> a -> StatePut 
writeSized n f x = do 
        incPos n 
        return (f x) 

writeInt32 :: Int -> StatePut 
writeInt32 = writeSized 32 putWord32be . fromIntegral 

writeBS :: BS.ByteString -> StatePut 
writeBS b = writeSized (BS.length b) putByteString b 

data SomeData = SomeData { 
    sdName :: BS.ByteString 
    , sdAge :: Int 
    , sdN :: Int 
} deriving (Show, Eq) 

instance StateBinary SomeData where 
    sput (SomeData nm a n) = do 
      writeBS nm 
      writeInt32 a 
      writeInt32 n 

testData = SomeData "TestName" 30 100 

runSPut :: StateBinary a => a -> BL.ByteString 
runSPut a = runPut $ S.evalState (sput a) initialState 

-- runSPut testData returns "\NUL\NUL\NULd" 
+0

'writeSized'란 무엇이고 어디에서 가져와야합니까? 이 코드는 나를 위해 컴파일되지 않습니다. – bheklilr

+0

죄송합니다. 복사 작업을 일괄 처리하여 실수로 건너 뛰었습니다. 지금 추가하십시오! – Compo

답변

2

문제는 writeSized이 실제로 바이트 스트림에 쓰지 않는다는 것입니다. returnPut 계산을 상태 모나드에 랩핑하며 실제로 실행하지는 않습니다. 이 그것을 해결하는 현명한 방법이 될 수 있지만 분명 하나 Put가 (실제로 PutM) 모나드라는 사실을 활용하고 상태 모나드로를 구성하는 모나드 변압기를 사용하는 것입니다 :

{-# LANGUAGE OverloadedStrings #-} 


import   Control.Applicative 
import qualified Control.Monad.State.Strict as S 
import   Data.Binary.Put 
import qualified Data.ByteString   as BS 
import qualified Data.ByteString.Lazy  as BL 

data SState = SState { 
    wsPosition :: Int 
    -- plus whatever else 
} 

initialState = SState 0 
-- S.StateT SState PutM is a composed monad, with a state layer above PutM. 
type StatePut = S.StateT SState PutM() 

class StateBinary a where 
    sput :: a -> StatePut 

incPos :: Int -> StatePut 
incPos amnt = do 
    (SState p) <- S.get 
    S.put $ SState (p + amnt) 

writeSized :: Int -> (a -> Put) -> a -> StatePut 
writeSized n f x = do 
        incPos n 
        -- lift runs a computation in the underlying monad. 
        S.lift (f x) 

writeInt32 :: Int -> StatePut 
writeInt32 = writeSized 32 putWord32be . fromIntegral 

writeBS :: BS.ByteString -> StatePut 
writeBS b = writeSized (BS.length b) putByteString b 

data SomeData = SomeData { 
    sdName :: BS.ByteString 
    , sdAge :: Int 
    , sdN :: Int 
} deriving (Show, Eq) 

instance StateBinary SomeData where 
    sput (SomeData nm a n) = do 
      writeBS nm 
      writeInt32 a 
      writeInt32 n 

testData = SomeData "TestName" 30 100 

runSPut :: StateBinary a => a -> BL.ByteString 
runSPut a = runPut $ S.evalStateT (sput a) initialState 

-- *Main> runSPut testData 
-- "TestName\NUL\NUL\NUL\RS\NUL\NUL\NULd" 
+0

우수, 완벽하게 작동합니다, 감사합니다! – Compo

1

당신이 할 수있는 bytestring Builder (편집 : 이제 대신 bytestring에서 binary에서 하나를 사용하여) : 사용

{-# LANGUAGE OverloadedStrings #-} 

import   Data.Monoid 
import qualified Data.Binary    as B 
import qualified Data.Binary.Builder  as BU 
import qualified Data.ByteString   as BS 
import qualified Data.ByteString.Lazy  as BL 

data SomeData = SomeData { 
    sdName :: BS.ByteString 
    , sdAge :: Int 
    , sdN :: Int 
} deriving (Show, Eq) 

testData :: SomeData 
testData = SomeData "TestName" 30 100 

renderData :: SomeData -> BU.Builder 
renderData (SomeData n a i) = mconcat $ 
    BU.fromByteString n : map (BU.fromLazyByteString . B.encode) [a,i] 

test :: BL.ByteString 
test = BU.toLazyByteString . renderData $ testData 

을 아이디어는 도입 (BU.fromX) 및 추가 작업이 O (1), 당신은 단지에 지불하도록되어 있다는 것입니다으로 다시 변환하면 끝납니다..

+0

감사합니다. 위의 상태 코드와 함께 구현해 보겠습니다. 이게 작동하려면 B.encode 호출이 작동하도록하기 위해 내 데이터 유형의 각 필드에 대해 Binary 인스턴스가 필요하다고 말하는 것이 맞습니까? – Compo

+0

필드를'Builder's로 변환하는 방법이 필요합니다. 'Data.Binary'는'ByteString','Word's,'Char's에 대해 이렇게하는 방법을 제공합니다. 'encode '를 사용하는 것은'Int'serialization (이것은 매우 잘못되기 쉽다 - 64 비트 기계에서는 잘림 오류가있을 수 있음)의 세부 사항을 숨기기 때문에 편리하지만'putWordX'를 사용할 수있다. fromintegral'을 사용하지 마십시오. – Fixnum

+0

또한 Binary 인스턴스를 만드는 것은 매우 쉽습니다 (http://hackage.haskell.org/package/binary-0.7.1.0/docs/Data-Binary.html#g:3). – Fixnum