将Conduit纳入普通功能

时间:2014-02-12 12:34:18

标签: haskell stream conduit

我写了一个简单的程序,其中我读了一个大的XML文件并做了一些 处理文件内容然后保存处理 新文件中的数据。

原始主要功能如下:

main = do
  content <- B.readFile "/home/sibi/github/data/chennai.osm" 
  let tags = removeUnwanted $ parseTags content 
      hospitals = toHospital $ extractHospitalNode tags
  BL.writeFile "osmHospitals.json" (encode hospitals)

但是这段代码占用了全部内存,需要花费大量时间才能完成。所以,我决定了 使用管道库使程序在恒定的内存中运行。

但在阅读conduit tutorial之后,我仍然没有得到这个想法 如何使上述程序使用管道库的功能。

我发现我可以使用管道sourceFile来传输。parseTags 文件的内容。但接下来如何应用函数here(这是TagSoup库中的函数)和其他简单函数 现在流媒体内容?

修改:整个代码为{{3}}

2 个答案:

答案 0 :(得分:3)

parseTags的方法与conduitpipes的方法之间存在巨大的脱节:parseTags假设它可以纯粹访问下一个数据块,而{{ {1}} / pipes可让您处理不可能发生的情况,例如从文件中流式传输。为了将解析混合到conduit / pipes中,您必须有一种方法可以将使用解析的步骤混合到可以提取新数据块的步骤中。

(我会在续集中使用conduit,因为我对它们比较熟悉,但这个想法是可以转移的。)

我们可以在类型中看到这种断开,但我会从略有限制的版本开始。

pipes

我们可以将parseTags :: Lazy.ByteString -> [Tag Lazy.ByteString] 视为流媒体设备,毕竟,基本上只是

Lazy.ByteString

这样,如果我们自己生成type LazyByteString = [Strict.ByteString] ,那么我们就可以依赖列表的懒惰来确保我们生成的内容不会超过Lazy.ByteString所需的内容(我会假设没有看,parseTags被写入,以便它可以逐步解析像这样的流式结构。

parseTags

现在问题在于列表的流式传输行为主要取决于能够生成列表纯粹的尾部。在到目前为止的讨论中,根本没有提到过monad。不幸的是,对于从文件中流式传输的字符串,情况并非如此 - 我们需要以某种方式在每个流式块之间集成IO操作,我们会考虑是否已达到EOF并根据需要关闭文件。

这正是sillyGen :: LazyByteString sillyGen = gen 10 where gen 0 = [] gen n = "<tag> </tag>" : gen (n-1) pipes的领域,所以我们来看看解决这个问题的方法。

conduit

我们可以认为-- from pipes-bytestring fromHandle :: Handle -> Producer' Strict.ByteString IO () 是“单一交织”等同于

fromHandle

这些类型表明这两项操作之间存在重大差异 - 当我们将Lazy.hGetContents :: Handle -> IO Lazy.ByteString 传递给hGetContents时,IO可以在一个Handle操作中执行pipes-bytestring它返回一个在fromHandle上参数化的类型,但不能简单地从中释放。这恰恰表明IO使用惰性IO(由于使用hGetContents而无法预测),而unsafeInterleaveIO使用确定性流式传输。

我们可以写一个与fromHandle类似的类型

Producer Strict.ByteString IO ()

换句话说,我们可以认为data IOStreamBS = IOSBS { stepStream :: IO (Strict.ByteString, Either IOStreamBS ()) } 只不过是一个IO动作,它恰好产生了文件的下一个块,并且(可能)产生了获取下一个块的新动作。这就是Producer Strict.ByteString IO ()pipes提供确定性流式传输的方式。

但这也意味着你无法一举逃离conduit - 你必须随身携带它。


我们可能因此希望调整IO,以便能够对其输入进行一些概括,只接受parseTags作为Producer Strict.ByteString IO ()类型

StringLike

让我们假设我们已经实例化了parseTags :: StringLike str => str -> [Tag str] 。这意味着将StringLike (Producer Strict.ByteString IO ())应用于我们的制作人将为我们提供parseTags的列表。

Tag (Producer Strict.ByteString IO ())

为了实现这一目标,我们不得不查看我们的type DetStream = Producer Strict.ByteString IO () parseTags :: DetStream -> [Tag DetStream] 并将其切割成块而不执行Producer monad中的任何内容。到目前为止,应该很清楚这样的函数是不可能的 - 我们甚至无法在IO中执行某些操作而从文件中获取第一个块。


为了解决这种情况,出现了像IOpipes-parse这样的系统,用更像

的东西替换了函数签名
pipes-group

这看起来很可怕但与parseTagsGrouped :: Producer Strict.ByteString IO () -> FreeT (Producer (Tag Strict.ByteString) IO) IO () 的用途相同,只是它将列表推广到允许我们在每个元素之间执行任意parseTags操作的结构。正如类型所示,这种转换可以纯粹地完成,因此允许我们使用纯组合来组装流媒体机器,并且当我们在最后执行它时仅产生IO步骤(使用IO )。


所以,所有的说法和完成,可能无法使用runEffectpipes流式传输到conduit ---它只是假设某些转换可以完成纯粹地,将所有parseTags推到一个时间点,而IO / pipes基本上是在整个计算过程中传播conduit而没有太多精神开销的机制。

但是,如果您使用IO,则只要您小心,就可以使用惰性parseTags。使用IO中的hGetContents尝试一些变体。主要问题是文件可能会在Data.ByteString.Lazy d操作实际到达之前关闭。因此,您需要非常谨慎地管理严格性。

基本上这是unsafeInterleaveIO / pipes和懒惰IO之间的巨大差异。当使用惰性IO时,所有“读取块”操作都被Haskell惰性隐藏和隐式控制。这是动态的,隐含的,难以观察或预测。在conduit / pipes中,所有这些动作都是非常明确和静态的,但是由您来管理复杂性。

答案 1 :(得分:-1)

如果您尝试System.IO,请逐行读取并处理它(或读取xml文件的某些部分),该怎么办?