经验丰富的Haskell开发人员在devise*时间如何处理懒惰?
我是一名中级Haskell程序员,拥有严格的FP和非FP语言的丰富经验。 我的大多数Haskell代码分析中等大的数据集(10 ^ 6..10 ^ 9),所以懒惰总是潜伏着。 我对thunk,WHNF,模式匹配和共享有相当好的理解,并且我已经能够用爆炸模式和seq来修复泄漏,但是这种简介和祈祷方法感觉肮脏和错误。
我想知道Haskell程序员在devise时如何接近懒惰。 我不是问如Data.ByteString.Lazy或foldl'; 相反,我想知道如何考虑导致运行时内存问题和棘手的debugging的较低级别的懒惰机制。
在devise期间,你如何思考thunk,模式匹配和共享?
你用什么样的devise模式和习语来避免泄漏?
你是怎么学习这些模式和习语的,你有没有好的参考?
你如何避免过早优化不泄漏的非问题?
(2014-05-15修订时间预算):
你预算大量的项目时间来寻找和解决内存问题?
或者,您的devise技巧通常是否会避免内存问题,并且在开发周期的早期就会获得预期的内存消耗?
我想大多数“严重泄漏”的麻烦是因为人们没有一个好的概念模型。 没有一个好的概念模型的哈斯克勒倾向于传播更严格的迷信。 也许这种直觉来自他们玩弄小例子和紧密循环的结果。 但这是不正确的。 在正确的时间偷懒,在正确的时间严格同样重要。
数据types有两个阵营,通常被称为“数据”和“数据”。 尊重每一个的模式是至关重要的。
- 产生“数据”(Int,ByteString,…)的操作必须被迫靠近它们发生的地方。 如果我给累加器添加一个数字,在添加另一个之前,我会小心地确保它会被强制。 对懒惰的一个很好的理解在这里是非常重要的,尤其是它的条件性质(即严格命题不是采取“
X
被评估”的forms,而是“ 当评估Y
时,Y
也是如此”)。 - 产生和消费 “数据”的操作(大部分时间列出,树,大多数其他recursiontypes)必须逐步执行。 通常codata – > codata转换应该为它们消耗的每一位信息产生一些信息(像
filter
一样跳过模)。 codata的另一个重要组成部分是,你可以在任何可能的情况下线性地使用它 – 例如只使用一个列表的尾部; 一次使用树的每个分支。 这确保了GC可以收集碎片。
当你有包含数据的数据时,事情需要特别的照顾。 例如iterate (+1) 0 !! 1000
iterate (+1) 0 !! 1000
在评估之前最终会产生一个1000的大小。 您需要再次考虑有条件的严格性 – 防止这种情况的方法是确保当清单的缺点被消耗时,会发生其元素的添加。 iterate
违反这个,所以我们需要一个更好的版本。
iterate' :: (a -> a) -> a -> [a] iterate' fx = x : (x `seq` iterate' f (fx))
当你开始撰写文章的时候,当遇到不好的情况时,很难分辨出来。 一般来说很难做出高效的数据结构/函数,在数据和数据上同样适用,重要的是要记住哪个是哪个(哪怕是在多态环境中没有得到保证的情况下,你应该有一个想法并尝试尊重它)。
分享是非常棘手的,我想我主要是根据具体情况来处理它。 因为这很棘手,所以我尽量保持本地化,不要将大数据结构暴露给模块用户。 这通常可以通过暴露组合器来生成所讨论的事物,然后一次性生产和消费(monads上的密度转换就是这样的一个例子)。
我的devise目标是让每个function都尊重我的types的数据/编码模式。 我通常可以打它(虽然有时需要一些沉重的想法 – 这些年来已经变得很自然),而且我很less有泄漏问题。 但我并不认为这很容易 – 它需要经验与规范的图书馆和语言模式。 这些决定并不是孤立的,所有事情都必须立即正确地执行。 一个不好调整的乐器可能毁了整个音乐会(这就是为什么“随机扰动优化”几乎不能解决这类问题)。
Apfelmus的空间不variables文章有助于进一步发展你的空间/ thunk直觉。 另请参阅下面的Edward Kmett的评论。