Труба, которая поддерживает состояние

Я пытаюсь вычислить скользящие хеш-значения (buzzhash) для большого файла, используя pipes.

В настоящее время у меня есть это. Но не знаю, как написать канал, который поддерживает состояние.

import qualified Data.ByteString.Lazy as L
import Data.Word
import Data.Bits(xor,rotate)
import Data.Array
import Pipes
import Control.Monad.State.Strict
import Control.Monad(forever)

produceFromList (x:xs) = do 
  yield x
  produceFromList xs

buzzHash = do
  x <- await
  h <- lift $ get -- pull out previous value
  let h' = rotate h 1 `xor` (hashArrW8!x) -- calculate new value
  lift $ put h' -- save new value 
  yield h'

stdoutLn :: Consumer Word64 IO ()
stdoutLn = do 
  a <- await 
  lift $ print a

main = do 
  bs <- L.unpack `fmap` L.getContents
  runEffect $ produceFromList bs >-> buzzHash >-> stdoutLn

hashArrW8 :: Array Word8 Word64

Как заставить buzzHash сохранить предыдущее значение и использовать его для расчета следующего значения? Начальное значение состояния должно быть 0.


person Dulguun Otgon    schedule 22.02.2016    source источник


Ответы (1)


Вы были почти там; вам просто нужно управлять государством.

main = do
  bs <- L.unpack `fmap` L.getContents
  flip execStateT 0 $ runEffect $ produceList bs >-> buzzHash >-> hoist lift stdoutLn

Я предполагаю, что вы не хотите восстанавливать состояние, поэтому я использую execStateT, а не runStateT.

Единственное любопытство здесь в том, что stdoutLn был помечен как Consumer Word64 IO () . Поэтому я использую hoist lift, чтобы сделать его Consumer Word64 (StateT Word64 IO) () Все в серии a >-> b >-> c должно согласовываться в базовой монаде и возвращаемом типе.

Вот еще несколько комментариев, которые могут сэкономить вам время. Первый produceFromList это each.

Более того, вы могли бы избежать hoist lift, переименовав свой stdoutLn:

stdoutLn :: MonadIO m => Consumer Word64 m ()
stdoutLn = do 
   a <- await 
   liftIO $ print a

Но вот беда: вы не повторяете действие. Это должен быть цикл:

stdoutLn :: MonadIO m => Consumer Word64 m ()
stdoutLn = do 
   a <- await 
   liftIO $ print a
   stdoutLn

на самом деле это уже доступно как P.print, поэтому мы можем написать

import qualified Pipes.Prelude as P
main = do
  bs <- L.unpack `fmap` L.getContents
  flip execStateT 0 $ runEffect $ each bs >-> buzzHash >-> P.print

Насколько я вас понимаю, buzzHash тоже должно повторяться бесконечно:

buzzHash = do
  x <- await
  h <- lift $ get -- pull out previous value
  let h' = rotate h 1 `xor` (hashArrW8!x) -- calculate new value
  lift $ put h' -- save new value 
  yield h'
  buzzHash

(это forever buzzHash, где мы используем ваше buzzHash)

Наконец, если вы

 import qualified Pipes.ByteString as PB
 import Control.Lens (view) -- (or Lens.Micro.MTL or Lens.Simple)

мы видим, что нам не нужен ленивый ввод-вывод строки байтов, который в любом случае не работает должным образом. Pipes.ByteString уже имеет нужный нам unpack, упакованный как линза, так что мы используем view PB.unpack там, где в других местах мы использовали бы B.unpack. Итак, в конце мы можем написать

main = flip evalStateT 0 $ runEffect $ view PB.unpack PB.stdin >-> buzzHash >-> P.print

Когда он находится в этой форме, мы видим, что не используем базовое состояние конвейера, кроме как в buzzHash, поэтому мы можем локализовать это.

import Pipes.Lift (evalStateP) 
main =  runEffect $ view PB.unpack PB.stdin >-> evalStateP 0 buzzHash >-> P.print

или, если хотите, можете переписать

buzzHash' :: Monad m => Word64 -> Pipe Word8 Word64 m r
buzzHash' n = evalStateP n $ forever $ do
    x <- await
    h <- lift $ get -- pull out previous value
    let h' = rotate h 1 `xor` (hashArrW8!x) -- calculate new value
    lift $ put h' -- save new value 
    yield h'

Тогда вы бы написали

main =  runEffect $ view PB.unpack PB.stdin >-> buzzHash' 0 >-> P.print
person Michael    schedule 22.02.2016
comment
все ли после buzzHash в монаде состояния? - person Dulguun Otgon; 22.02.2016
comment
Три трубочки, связанные >->, все трансформируются (StateT Word64 IO), да. Таким образом, buzzHash специализируется на Pipe Word8 Word64 (StateT Word64 IO) (), P.print специализируется на Consumer Word64 (StateT Word64 IO) (), а view PB.unpack PB.stdin на Producer Word8 (StateT Word64 IO) () на использовании синонимов типов, которые использует канал. Поэтому, когда вы склеиваете их вместе, вы получаете Effect (StateT Word64 IO) (). Итак, когда вы применяете runEffect, вы получаете StateT Word64 IO (). А затем, когда вы применяете runStateT, у вас есть Word64 -> IO ((),Word64). - person Michael; 22.02.2016
comment
Я добавил немного о evalStateP и т. д. в конце. Может быть, это больше подходит, так как не требует настройки конвейера, чтобы все согласовывалось. Вместо этого мы просто пишем функцию buzzHash, которая явно не использует StateT в типе. - person Michael; 22.02.2016