2013-01-15 64 views
3

我在想Haskell中的列表,并且我认为在其他语言中,一个不使用列表的一切。当然,如果你以后需要这些值,你可能想要存储一个列表,但是如果它只是一次性的,比如从[1..n]开始迭代,为什么使用一个列表,其中真正需要的变量是一个增加的变量?管道,替换列表?

我还阅读了关于“列表融合”的内容,并指出尽管Haskell编译器试图实现这种优化以消除中间列表,但它们往往不成功,导致垃圾收集器不得不清理仅被使用一次的列表。

另外,如果你不小心一个可以轻松地共享一个列表,这意味着垃圾收集不清理,这可能会导致与以前的设计在不断运行的算法运行内存空间。

所以我认为这将是最好的,以避免完全名单,至少在一个实际上并不想“商店”名单。

我然后在conduit来了,它是说:

解决数据流的问题,允许生产, 改造,并在不断的 存储器中的数据流的消费。

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

例如,我可以做到以下几点:

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

而且有一些适当放置类型标注,使用管道的整个过程,而不是名单?

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

+1

你对名单的直觉是不正确的,我想,你也可以忘记了懒惰的评价,当你谈论“存储”一名单。在任何情况下,“管道”正在解决的问题都是懒惰IO提出的问题。也看看'vector'软件包。 – jberryman

回答

8

列表计算在不断的内存流相同的情况下,他们会为conduit。中间数据结构的存在或不存在并不影响它是否在恒定内存中运行。它所改变的只是它所居住的恒定记忆的效率和大小。

不要期望导管运行内存少于等效列表计算。它实际上应该占用更多内存,因为导管步骤比列表单元有更大的开销。另外,导管目前没有流融合。前段时间有人did experiment with that,虽然没有纳入图书馆。另一方面,列表在很多情况下可以并且确实会融合以去除中间数据结构。

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

+0

因此导管不够智能,无法将'map f $ map g'结合到'map(f。g)'中? – Clinton

+1

@Clinton原则上你可以添加一个重写规则,将左侧转换为右侧,但是我为自己的'pipes'库尝试了类似的东西,并且它不能可靠地触发。然而,我链接的流式融合版本实际上会自动优化左侧到右侧,而不会有任何提示,但是这带来了它自己的问题。问题的核心在于'ghc'并不是真正擅长收紧循环,这就是为什么人们首先想到流融合,以便将递归代码转换为非递归代码。 –

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"