Haskell 管道:有一个接收器 return 一个基于上游值的值
Haskell Conduit: having a Sink return a value based on the values from upstream
我一直在尝试使用 Conduit 库来做一些简单的 I/O 涉及文件,但我遇到了困难。
我有一个文本文件,只包含一些数字,例如 1234
。我有一个使用 readFile
(无管道)和 returns Maybe Int
读取文件的函数(当文件实际上不存在时返回 Nothing
)。我正在尝试编写此函数的一个使用管道的版本,但我无法弄明白。
这是我的:
import Control.Monad.Trans.Resource
import Data.Conduit
import Data.Functor
import System.Directory
import qualified Data.ByteString.Char8 as B
import qualified Data.Conduit.Binary as CB
import qualified Data.Conduit.Text as CT
import qualified Data.Text as T
myFile :: FilePath
myFile = "numberFile"
withoutConduit :: IO (Maybe Int)
withoutConduit = do
doesExist <- doesFileExist myFile
if doesExist
then Just . read <$> readFile myFile
else return Nothing
withConduit :: IO (Maybe Int)
withConduit = do
doesExist <- doesFileExist myFile
if doesExist
then runResourceT $ source $$ conduit =$ sink
else return Nothing
where
source :: Source (ResourceT IO) B.ByteString
source = CB.sourceFile myFile
conduit :: Conduit B.ByteString (ResourceT IO) T.Text
conduit = CT.decodeUtf8
sink :: Sink T.Text (ResourceT IO) (Maybe Int)
sink = awaitForever $ \txt -> let num = read . T.unpack $ txt :: Int
in -- I don't know what to do here...
有人可以帮我完成 sink
功能吗?
谢谢!
这并不是一个很好的例子,说明管道实际上提供了很多价值,至少不是你现在看待它的方式。具体来说,您正在尝试使用 read
函数,该函数要求整个值都在内存中。此外,您当前的错误处理行为有点松散。本质上,如果内容中有任何意外,您将收到 read: no parse
错误。
然而,是一种我们可以在管道中使用它并且有意义的方法:通过我们自己逐字节解析ByteString
并避免read
函数。幸运的是,这种模式属于标准的左折叠,管道组合器包提供了一个完美的功能(管道中的元素左折叠,又名 foldlCE
):
{-# LANGUAGE OverloadedStrings #-}
import Conduit
import Data.Word8
import qualified Data.ByteString as S
sinkInt :: Monad m => Consumer S.ByteString m Int
sinkInt =
foldlCE go 0
where
go total w
| _0 <= w && w <= _9 =
total * 10 + (fromIntegral $ w - _0)
| otherwise = error $ "Invalid byte: " ++ show w
main :: IO ()
main = do
x <- yieldMany ["1234", "5678"] $$ sinkInt
print x
有很多注意事项:如果有意外字节,它只会抛出异常,并且它根本不处理整数溢出(尽管修复了 that 只是将 Int
替换为 Integer
的问题)。重要的是要注意,由于有效的 32 位或 64 位 int 在内存中的字符串表示总是很小,因此管道对于这个问题来说是过大的杀伤力,尽管我希望这段代码能提供一些指导,说明如何通常编写管道代码。
我一直在尝试使用 Conduit 库来做一些简单的 I/O 涉及文件,但我遇到了困难。
我有一个文本文件,只包含一些数字,例如 1234
。我有一个使用 readFile
(无管道)和 returns Maybe Int
读取文件的函数(当文件实际上不存在时返回 Nothing
)。我正在尝试编写此函数的一个使用管道的版本,但我无法弄明白。
这是我的:
import Control.Monad.Trans.Resource
import Data.Conduit
import Data.Functor
import System.Directory
import qualified Data.ByteString.Char8 as B
import qualified Data.Conduit.Binary as CB
import qualified Data.Conduit.Text as CT
import qualified Data.Text as T
myFile :: FilePath
myFile = "numberFile"
withoutConduit :: IO (Maybe Int)
withoutConduit = do
doesExist <- doesFileExist myFile
if doesExist
then Just . read <$> readFile myFile
else return Nothing
withConduit :: IO (Maybe Int)
withConduit = do
doesExist <- doesFileExist myFile
if doesExist
then runResourceT $ source $$ conduit =$ sink
else return Nothing
where
source :: Source (ResourceT IO) B.ByteString
source = CB.sourceFile myFile
conduit :: Conduit B.ByteString (ResourceT IO) T.Text
conduit = CT.decodeUtf8
sink :: Sink T.Text (ResourceT IO) (Maybe Int)
sink = awaitForever $ \txt -> let num = read . T.unpack $ txt :: Int
in -- I don't know what to do here...
有人可以帮我完成 sink
功能吗?
谢谢!
这并不是一个很好的例子,说明管道实际上提供了很多价值,至少不是你现在看待它的方式。具体来说,您正在尝试使用 read
函数,该函数要求整个值都在内存中。此外,您当前的错误处理行为有点松散。本质上,如果内容中有任何意外,您将收到 read: no parse
错误。
然而,是一种我们可以在管道中使用它并且有意义的方法:通过我们自己逐字节解析ByteString
并避免read
函数。幸运的是,这种模式属于标准的左折叠,管道组合器包提供了一个完美的功能(管道中的元素左折叠,又名 foldlCE
):
{-# LANGUAGE OverloadedStrings #-}
import Conduit
import Data.Word8
import qualified Data.ByteString as S
sinkInt :: Monad m => Consumer S.ByteString m Int
sinkInt =
foldlCE go 0
where
go total w
| _0 <= w && w <= _9 =
total * 10 + (fromIntegral $ w - _0)
| otherwise = error $ "Invalid byte: " ++ show w
main :: IO ()
main = do
x <- yieldMany ["1234", "5678"] $$ sinkInt
print x
有很多注意事项:如果有意外字节,它只会抛出异常,并且它根本不处理整数溢出(尽管修复了 that 只是将 Int
替换为 Integer
的问题)。重要的是要注意,由于有效的 32 位或 64 位 int 在内存中的字符串表示总是很小,因此管道对于这个问题来说是过大的杀伤力,尽管我希望这段代码能提供一些指导,说明如何通常编写管道代码。