我正在考虑Haskell中的列表,我想在其他语言中,一个人不会使用列表。当然,如果稍后需要值,你可能想要存储一个列表,但如果它只是一个关闭,比如从[1..n]
迭代,为什么要使用一个列表,其中所有真正需要的是一个递增的变量? / p>
我还读到了“列表融合”,并指出虽然Haskell编译器尝试实现此优化以消除中间列表,但它们通常不成功,导致垃圾收集器必须清理仅使用一次的列表。
另外,如果你不小心可以轻松共享一个列表,这意味着垃圾收集器不会清理它,这可能会导致内存耗尽的算法以前设计为在恒定空间中运行
所以我认为最好完全避免列表,至少当一个人实际上并不想“存储”列表时。
然后我遇到了conduit
,它说是:
流数据问题的解决方案,允许生产, 转换和恒定数据流的消耗 存储器中。
这听起来很完美。我知道conduit
是针对资源获取和发布问题的IO
问题而设计的,但是只能将其用作代替列表的代替吗?
例如,我可以执行以下操作:
fold f3 $ take 10 $ map f2 $ unfold f1 init_value
使用一些适当放置的类型注释,使用整个过程的管道而不是列表?
我希望也许classy-prelude
允许这样的代码,但我不确定。如果有可能,有人可以举个例子吗,就像上面说的一样?
答案 0 :(得分:8)
在与conduit
相同的情况下,在常量内存中列出计算流。中间数据结构的存在与否不会影响它是否在恒定的内存中运行。它所改变的只是它所栖息的恒定记忆的效率和大小。
不要指望管道在比等效列表计算更少的内存中运行。它实际上应该占用更多内存,因为管道步骤比列表单元格具有更大的开销。此外,导管目前没有流融合。有人did experiment with that前段时间,虽然没有纳入图书馆。另一方面,列表可以并且在许多情况下融合以移除中间数据结构。
要记住的重要一点是,流媒体并不一定意味着砍伐森林(即删除中间数据结构)。
答案 1 :(得分: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"