2012-06-16 4 views
3

Я начинаю Хаскелл, пытаясь обернуть голову вокруг библиотеки кабелепроводов.Как пронумеровать строки, считанные из файла, используя кабелепроводы?

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

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" 

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

$ 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 

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

ответ

2
close state = return state 

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

close _ = return [] 

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

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

+0

Спасибо! Я должен был больше обратить внимание на типы. – donatello

6

Да, это можно сделать. Я предпочитаю использовать вспомогательные функции в 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" 
+0

Спасибо, это работает. Но я хотел бы знать, что не так с моим кодом. Идея использовать UTF8 действительно полезна. – donatello

0

Альтернативное решение с pipes 3.0, хотя это использует строку вместо ByteString. Главное преимущество в моем уме - это возможность использовать обычные методы монады-монады. Другим преимуществом является то, что номер стартовой строки не скрыт в 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 

Узнайте, как сделать более детализированное управление ресурсами в объявляем blog post of pipes-safe..