【问题标题】:Attoparsec IterateeAttoparsec Iteratee
【发布时间】:2011-06-15 16:22:32
【问题描述】:

我想,只是为了了解一些关于 Iteratees 的知识,使用 Data.Iteratee 和 Data.Attoparsec.Iteratee 重新实现我制作的一个简单的解析器。不过,我很难过。下面我有一个简单的例子,它能够从文件中解析 one 行。我的解析器一次读取一行,所以我需要一种向迭代器提供行的方法,直到它完成。我已经阅读了我在谷歌上搜索到的所有内容,但是很多关于 iteratee/enumerators 的材料都非常先进。这是重要的代码部分:

-- There are more imports above.
import Data.Attoparsec.Iteratee
import Data.Iteratee (joinI, run)
import Data.Iteratee.IO (defaultBufSize, enumFile)

line :: Parser ByteString -- left the implementation out (it doesn't check for 
                             new line)

iter = parserToIteratee line

main = do
    p <- liftM head getArgs
    i <- enumFile defaultBufSize p $ iter
    i' <- run i
    print i'

此示例将解析并打印多行文件中的一行。原始脚本将解析器映射到 ByteStrings 列表。所以我想在这里做同样的事情。我在 Iteratee 中找到了enumLines,但我终生无法弄清楚如何使用它。也许我误解了它的目的?

【问题讨论】:

    标签: haskell iterate attoparsec


    【解决方案1】:

    由于您的解析器一次只处理一行,因此您甚至不需要使用 attoparsec-iteratee。我会这样写:

    import Data.Iteratee as I
    import Data.Iteratee.Char
    import Data.Attoparsec as A
    
    parser :: Parser ParseOutput
    type POut = Either String ParseOutput
    
    processLines :: Iteratee ByteString IO [POut]
    processLines = joinI $ (enumLinesBS ><> I.mapStream (A.parseOnly parser)) stream2list
    

    理解这一点的关键是“枚举”,它只是流转换器的迭代术语。它采用一种流类型的流处理器(迭代器)并将其转换为与另一种流一起使用。 enumLinesBSmapStream 都是枚举对象。

    要将解析器映射到多行,mapStream 就足够了:

    i1 :: Iteratee [ByteString] IO (Iteratee [POut] IO [POut]
    i1 = mapStream (A.parseOnly parser) stream2list
    

    嵌套的迭代器只是意味着这会将[ByteString] 的流转换为[POut] 的流,并且当运行最终的迭代器(stream2list)时,它会将该流返回为[POut]。所以现在你只需要 lines 的 iteratee 等价物来创建 [ByteString] 的流,这就是 enumLinesBS 所做的:

    i2 :: Iteratee ByteString IO (Iteratee [ByteString] IO (Iteratee [POut] m [POut])))
    i2 = enumLinesBS $ mapStream f stream2list
    

    但是由于所有的嵌套,这个函数使用起来非常笨拙。我们真正想要的是一种直接在流转换器之间管道输出的方法,最后将所有内容简化为单个迭代。为此,我们使用joinI(&gt;&lt;&gt;)(&gt;&lt;&gt;)

    e1 :: Iteratee [POut] IO a -> Iteratee ByteString IO (Iteratee [POut] IO a)
    e1 = enumLinesBS ><> mapStream (A.parseOnly parser)
    
    i' :: Iteratee ByteString IO [POut]
    i' = joinI $ e1 stream2list
    

    这和我上面写的一样,内联e1

    但仍有重要元素。此函数仅以列表的形式返回解析结果。通常,您会想做其他事情,例如将结果与折叠合并。

    编辑:Data.Iteratee.ListLike.mapM_ 通常对于创建消费者很有用。此时流的每个元素都是一个解析结果,所以如果你想打印它们,你可以使用

    consumeParse :: Iteratee [POut] IO ()
    consumeParse = I.mapM_ (either (\e -> return ()) print)
    
    processLines2 :: Iteratee ByteString IO ()
    processLines2 = joinI $ (enumLinesBS ><> I.mapStream (A.parseOnly parser)) consumeParse
    

    这将只打印成功的解析。您可以轻松地向 STDERR 报告错误,或以其他方式处理它们。

    【讨论】:

    • 惊人的答案,希望我能投票两次!我可以问一个如何写那个消费者的例子吗?假设我要做的就是打印成功的解析结果,如果这是一个简单的例子。
    • @shintoist:我现在已经添加了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多