Как пронумеровать строки, прочитанные из файла, с помощью каналов?

Я новичок в Haskell и пытаюсь осмыслить библиотеку каналов.

Я пробовал что-то подобное, но он не компилируется:

import Data.Conduit
import Data.Conduit.Binary as CB
import Data.ByteString.Char8 as BS

numberLine :: Monad m => Conduit BS.ByteString m BS.ByteString
numberLine = conduitState 0 push close
  where
    push lno input = return $ StateProducing (lno + 1) [BS.pack (show lno ++ BS.unpack input)]
    close state = return state

main = do
  runResourceT $ CB.sourceFile "wp.txt" $= CB.lines $= numberLine $$ CB.sinkFile "test.txt"

Похоже, что состояние в pipeitState должно быть того же типа, что и тип входа канала. По крайней мере, это то, что я понял из сообщения об ошибке:

$ ghc --make exp.hs
[1 of 1] Compiling Main             ( exp.hs, exp.o )

exp.hs:8:27:
    Could not deduce (Num [ByteString]) arising from the literal `0'
    from the context (Monad m)
      bound by the type signature for
                 numberLine :: Monad m => Conduit ByteString m ByteString
      at exp.hs:(8,1)-(11,30)
    Possible fix:
      add (Num [ByteString]) to the context of
        the type signature for
          numberLine :: Monad m => Conduit ByteString m ByteString
      or add an instance declaration for (Num [ByteString])
    In the first argument of `conduitState', namely `0'
    In the expression: conduitState 0 push close
    In an equation for `numberLine':
        numberLine
          = conduitState 0 push close
          where
              push lno input
                = return
                  $ StateProducing (lno + 1) [pack (show lno ++ unpack input)]
              close state = return state

Как это можно сделать с помощью трубопроводов? Я хочу читать строки из файла и добавлять номер строки к каждой строке.


person donatello    schedule 16.06.2012    source источник


Ответы (3)


close state = return state

В этом заключается ошибка типа. Ваша функция close должна иметь тип (state -> m [output]) (согласно документы). В вашем случае state = Int (вы можете добавить аннотации типов, чтобы убедиться, что он выбрал Int) и output = BS.ByteString, поэтому, вероятно, просто верните пустой список, поскольку в момент закрытия канала вы действительно не сохранили никаких ByteString для создания или ничего подобного.

close _ = return []

Особое примечание из документов для этого аргумента:

Состояние не нужно возвращать, так как оно больше не будет использоваться

person Dan Burton    schedule 18.06.2012
comment
Спасибо! Надо было больше внимания уделять типам. - person donatello; 18.06.2012

Да, это может быть сделано. Я предпочитаю использовать вспомогательные функции в Data.Conduit.List и также избегать Data.ByteString.Char8, если это вообще возможно. Я предполагаю, что ваш файл закодирован в UTF-8.

import Data.Conduit
import Data.Conduit.Binary as CB
import Data.Conduit.List as Cl
import Data.Conduit.Text as Ct
import Data.Monoid ((<>))
import Data.Text as T

numberLine :: Monad m => Conduit Text m Text
numberLine = Cl.concatMapAccum step 0 where
  format input lno = T.pack (show lno) <> T.pack " " <> input <> T.pack "\n"
  step input lno = (lno+1, [format input lno])

main :: IO ()
main =
  runResourceT
     $ CB.sourceFile "wp.txt"
    $$ Ct.decode Ct.utf8
    =$ Ct.lines
    =$ numberLine
    =$ Ct.encode Ct.utf8
    =$ CB.sinkFile "test.txt"
person Nathan Howell    schedule 16.06.2012
comment
Спасибо, это работает. Но я хотел бы знать, что не так с моим кодом. Идея использовать UTF8 действительно полезна. - person donatello; 18.06.2012

Альтернативное решение с pipe 3.0, хотя оно использует строку вместо ByteString. На мой взгляд, главное преимущество - это возможность использовать методы монад нормального состояния get и put. Еще одно преимущество заключается в том, что номер начальной строки не скрывается в addLineNumber (numberLine), поэтому легче начать с любого заданного номера строки.

import System.IO
import Data.Monoid ((<>))
import Control.Proxy
import qualified Control.Proxy.Trans.State as S

addLineNumber r = forever $ do
    n <- S.get
    line <- request r -- request line from file
    respond $ show n <> " " <> line
    S.put (n + 1) -- increments line counter

main = 
    withFile "wp.txt" ReadMode    $ \fin  ->
    withFile "test.txt" WriteMode $ \fout ->
    runProxy $ S.execStateK 1 -- start at line number at 1
             $ hGetLineS fin >-> addLineNumber >-> hPutStrLnD fout

Узнайте, как сделать более детализированное управление ресурсами, в объявлении сообщение в блоге о безопасности труб..

person Davorak    schedule 19.12.2012