3

我是一名 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"

看来,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

如何使用管道来做到这一点?我想从文件中读取行并将行号附加到每一行。

4

3 回答 3

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"
于 2012-06-16T18:29:22.230 回答
2
close state = return state

这就是类型错误。您的close函数应该具有类型(state -> m [output])(根据文档)。在您的情况下state = Int(您可能希望添加类型注释以确保它选择Int)和output = BS.ByteString,因此可能只返回空列表,因为在关闭管道时,您还没有真正保存任何ByteString要生产的 s 或类似的东西.

close _ = return []

特别注意该论点的文档:

不需要返回状态,因为它不会再次使用

于 2012-06-18T09:37:19.017 回答
0

管道 3.0的替代解决方案,尽管它确实使用字符串而不是 ByteString。在我看来,主要优势是能够使用正常状态的 monad 方法 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

在管道安全的公告博客文章中了解如何进行更细粒度的资源管理。.

于 2012-12-19T22:03:09.890 回答