3

我在考虑 Haskell 中的列表,我认为在其他语言中,不会对所有内容都使用列表。当然,如果您稍后需要这些值,您可能想要存储一个列表,但如果它只是一个关闭,比如说从 迭代[1..n],为什么要使用一个真正需要的只是一个递增变量的列表?

我还阅读了有关“列表融合”的内容,并注意到虽然 Haskell 编译器尝试实现这种优化以消除中间列表,但它们通常不成功,导致垃圾收集器不得不清理只使用一次的列表。

此外,如果您不小心,很容易共享一个列表,这意味着垃圾收集器不会清理它,这可能导致内存不足,而以前设计为在恒定空间中运行的算法。

所以我认为最好完全避免列表,至少当一个人实际上不想“存储”列表时。

然后我遇到了conduit,上面写着:

流数据问题的解决方案,允许在恒定内存中生产、转换和消费数据流。

这听起来很完美。我知道conduit它是为IO资源获取和释放问题而设计的,但是可以将它用作列表的替代品吗?

例如,我可以执行以下操作:

fold f3 $ take 10 $ map f2 $ unfold f1 init_value

并且通过一些适当放置的类型注释,在整个过程中使用管道而不是列表?

我希望这可能classy-prelude会允许这样的代码,但我不确定。如果可能的话,有人可以举个例子,像上面说的那样吗?

4

2 回答 2

8

在与conduit. 中间数据结构的存在与否不影响它是否在常量内存中运行。它所改变的只是它所驻留的常量内存的效率和大小。

不要期望管道在比等效列表计算更少的内存中运行。它实际上应该占用更多内存,因为管道步骤比列表单元具有更大的开销。此外,导管目前没有流融合。前段时间有人对此进行了实验,尽管没有将其合并到库中。另一方面,列表可以并且确实在许多情况下融合以删除中间数据结构。

要记住的重要一点是,流式传输并不一定意味着砍伐森林(即删除中间数据结构)。

于 2013-01-15T04:13:36.770 回答
4

conduit绝对不是为这种用例设计的,但理论上可以这样使用。我亲自为这个markdown包裹做的,在那里有额外的conduit管道比直接处理列表更方便。

如果把它和 放在一起classy-prelude-conduit,就可以得到一些比较简单的代码。我们当然可以向 classy-prelude-conduit 添加更多导出,以更好地优化此用例。现在,这是一个遵循您上面列出的基本要点的示例:

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
import ClassyPrelude.Conduit
import Data.Conduit.List (unfold, isolate)
import Data.Functor.Identity (runIdentity)

main = putStrLn
     $ runIdentity
     $ unfold f1 init_value
    $$ map f2
    =$ isolate 10
    =$ fold f3 ""

f1 :: (Int, Int) -> Maybe (Int, (Int, Int))
f1 (x, y) = Just (x, (y, x + y))

init_value = (1, 1)

f2 :: Int -> Text
f2 = show

f3 :: Text -> Text -> Text
f3 x y = x ++ y ++ "\n"
于 2013-01-15T04:18:01.063 回答