2015-03-07 4 views
3

Я пытался использовать библиотеку Conduit для создания простых файлов ввода-вывода, но мне тяжело.Haskell Conduit: наличие раковины возвращает значение, основанное на значениях от восходящего потока

У меня есть текстовый файл, содержащий только несколько цифр, таких как 1234. У меня есть функция, которая читает файл, используя readFile (без каналов), и возвращает 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? Спасибо!

ответ

3

Это не очень хороший пример того, где кабель действительно обеспечивает большую ценность, по крайней мере, не так, как вы смотрите на него прямо сейчас. В частности, вы пытаетесь использовать функцию 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 

Есть много предостережений, которые идут наряду с этим: он просто выдает исключение, если есть неожиданные байты, и он вообще не обрабатывает целочисленное переполнение (хотя исправление означает, что - это вопрос замены Int на Integer). Важно отметить, что, поскольку строковое представление в правильной 32- или 64-разрядной int-памяти всегда будет крошечным, кабельная передача является чрезмерной для этой проблемы, хотя я надеюсь, что этот код дает некоторые рекомендации о том, как обычно напишите код кабелепровода.

+0

Привет! Спасибо за ваш ответ! Действительно, до сих пор я пытался использовать Conduit для операций, которые неизбежно требуют, чтобы все данные были в памяти сразу, поэтому я, вероятно, не применяю библиотеку к ситуациям, которые она должна была обрабатывать сама по себе. Спасибо за предоставление образца кода. –

+0

Нет проблем с этим в целях обучения, на самом деле это хороший подход. Я просто хотел пояснить, что это не отличный пример соотношения мощности и веса кабелепровода. –