Ленивое декодирование списка с помощью Data.Binary
Я лениво кодирую списки, используя этот код (взят из этого ТАК вопроса):
import Data.Binary
newtype Stream a = Stream { unstream :: [a] }
instance Binary a => Binary (Stream a) where
put (Stream []) = putWord8 0
put (Stream (x:xs)) = putWord8 1 >> put x >> put (Stream xs)
Проблема в том, что реализация декодирования не ленива:
get = do
t <- getWord8
case t of
0 -> return (Stream [])
1 -> do x <- get
Stream xs <- get
return (Stream (x:xs))
Мне кажется, это должно быть лениво, но если мы запустим этот тестовый код:
head $ unstream (decode $ encode $ Stream [1..10000000::Integer] :: Stream Integer)
использование памяти взрывается. По какой-то причине он хочет декодировать весь список, прежде чем дать мне посмотреть на первый элемент.
Почему это не лень, и как я могу сделать это ленивым?
1 ответ
Это не лень, потому что Get
Монада является монадой строгого состояния (в двоичных версиях от 0.5.0.2 до 0.5.1.1; раньше она была монадой с отложенным состоянием, а в двоичной версии 0.6.* она стала монадой продолжения, я не анализировал последствия строгости этого менять):
-- | The parse state
data S = S {-# UNPACK #-} !B.ByteString -- current chunk
L.ByteString -- the rest of the input
{-# UNPACK #-} !Int64 -- bytes read
-- | The Get monad is just a State monad carrying around the input ByteString
-- We treat it as a strict state monad.
newtype Get a = Get { unGet :: S -> (# a, S #) }
-- Definition directly from Control.Monad.State.Strict
instance Monad Get where
return a = Get $ \s -> (# a, s #)
{-# INLINE return #-}
m >>= k = Get $ \s -> case unGet m s of
(# a, s' #) -> unGet (k a) s'
{-# INLINE (>>=) #-}
таким образом, последний рекурсивный
get >>= \x ->
get >>= \(Stream xs) ->
return (Stream (x:xs))
заставляет весь Stream
чтобы прочитать, прежде чем он может быть возвращен.
Я не думаю, что можно лениво декодировать Stream
в Get
монада (так что тем более не с Binary
пример). Но вы можете написать функцию отложенного декодирования, используя runGetState
:
-- | Run the Get monad applies a 'get'-based parser on the input
-- ByteString. Additional to the result of get it returns the number of
-- consumed bytes and the rest of the input.
runGetState :: Get a -> L.ByteString -> Int64 -> (a, L.ByteString, Int64)
runGetState m str off =
case unGet m (mkState str off) of
(# a, ~(S s ss newOff) #) -> (a, s `join` ss, newOff)
Сначала напишите Get
синтаксический анализатор, который возвращает Maybe a
,
getMaybe :: Binary a => Get (Maybe a)
getMaybe = do
t <- getWord8
case t of
0 -> return Nothing
_ -> fmap Just get
затем используйте это, чтобы сделать функцию типа (ByteString,Int64) -> Maybe (a,(ByteString,Int64))
:
step :: Binary a => (ByteString,Int64) -> Maybe (a,(ByteString,Int64))
step (xs,offset) = case runGetState getMaybe xs offset of
(Just v, ys, newOffset) -> Just (v,(ys,newOffset))
_ -> Nothing
и тогда вы можете использовать Data.List.unfoldr
лениво расшифровать список,
lazyDecodeList :: Binary a => ByteString -> [a]
lazyDecodeList xs = unfoldr step (xs,0)
и завернуть это в Stream
lazyDecodeStream :: Binary a => ByteString -> Stream a
lazyDecodeStream = Stream . lazyDecodeList