【问题标题】:How do experienced Haskell developers approach laziness at *design* time?经验丰富的 Haskell 开发人员如何在*设计*时处理惰性?
【发布时间】:2014-04-24 22:22:22
【问题描述】:

我是一名中级 Haskell 程序员,在严格的 FP 和非 FP 语言方面拥有大量经验。我的大多数 Haskell 代码都分析中等大小的数据集(10^6..10^9 东西),所以懒惰总是潜伏着。我对 thunk、WHNF、模式匹配和共享有相当好的理解,并且我已经能够使用 bang 模式和 seq 修复泄漏,但是这种配置文件和祈祷的方法让人感觉肮脏和错误。

我想知道有经验的 Haskell 程序员是如何在设计时处理懒惰的。我不是在问简单的项目,比如 Data.ByteString.Lazy 或 foldl';相反,我想知道您如何看待导致运行时内存问题和棘手调试的低级惰性机制。

您如何看待设计时的 thunk、模式匹配和共享?

您使用哪些设计模式和惯用语来避免泄漏?

你是如何学习这些模式和习语的,你有一些好的参考吗?

如何避免过早优化非泄漏非问题?

(2014-05-15 修订时间预算):

您是否将大量的项目时间用于查找和修复内存问题?

或者,您的设计技能是否通常会规避内存问题,并且您会在开发周期的早期获得预期的内存消耗?

【问题讨论】:

  • 这在很大程度上取决于您正在开发的应用程序类型。
  • 如果您在为特定问题设计解决方案方面更加具体,则此问题可能会在流量和答案方面看到更多。就目前而言,它非常广泛,而且离题很近。就个人而言,我很乐意看到这个问题得到解答。
  • 我在设计时不会想到这些东西。它们是实现细节。 耸耸肩
  • 可能更适合programmers.stackexchange.com,因为它比特定问题更具概念性。似乎属于“开发方法和流程”,这是一个明确列出的适合程序员的主题。

标签: haskell design-patterns memory-leaks lazy-evaluation


【解决方案1】:

我认为“严格性泄漏”的大部分问题都是因为人们没有一个好的概念模型。没有良好概念模型的 Haskeler 倾向于拥有并传播“越严格越好”的迷信。也许这种直觉来自他们玩弄小例子和紧密循环的结果。但这是不正确的。在正确的时间偷懒与在正确的时间严格同样重要。

数据类型有两大阵营,通常称为“数据”和“余数据”。尊重每个人的模式至关重要。

  • 产生“数据”(Int、ByteString、...)的操作必须强制靠近它们发生的位置。如果我向累加器添加一个数字,我会小心确保在添加另一个数字之前它会被强制执行。在这里,对惰性的良好理解非常重要,尤其是它的条件性质(即,严格性命题不采用“X 被评估”的形式,而是“ Y 被评估,所以是X")。
  • 产生和使用“codata”(大部分时间列出、树、大多数其他递归类型)的操作必须以增量方式进行。通常 codata -> codata 转换应该为它们消耗的每一位信息产生一些信息(模跳过,如filter)。 codata 的另一个重要部分是尽可能线性地使用它——即,只使用列表的尾部一次;只使用一棵树的每个分支一次。这可确保 GC 可以在使用碎片时收集碎片。

当您拥有包含数据的 codata 时,需要特别注意。例如。 iterate (+1) 0 !! 1000 在评估它之前最终会产生一个大小为 1000 的 thunk。您需要再次考虑条件严格性——防止这种情况的方法是确保 使用列表的一个 cons 时,会添加其元素。 iterate 违反了这一点,所以我们需要一个更好的版本。

iterate' :: (a -> a) -> a -> [a]
iterate' f x = x : (x `seq` iterate' f (f x))

当您开始编写内容时,当然很难判断何时会发生不良情况。一般来说,很难使高效的数据结构/函数在数据和余数据上同样有效,重要的是要记住哪个是哪个(即使在不能保证的多态环境中,你也应该记住一个并尝试尊重它)。

分享很棘手,我想我主要是根据具体情况来处理它。因为它很棘手,所以我尽量保持本地化,一般不向模块用户公开大型数据结构。这通常可以通过暴露组合子来生成有问题的东西,然后一次性生产和消费它(单子上的共密度变换就是一个例子)。

我的设计目标是让每个函数都尊重我的类型的数据/余数据模式。我通常可以击中它(尽管有时它需要一些深思熟虑 - 这些年来它已经变得很自然),而且我很少遇到泄漏问题。但我并不认为这很容易——它需要使用规范库和语言模式的经验。这些决定不是孤立地做出的,一切都必须立即正确才能正常工作。一种调音不佳的乐器可能会毁掉整个音乐会(这就是为什么“随机扰动优化”几乎无法解决这类问题的原因)。

Apfelmus 的Space Invariants 文章有助于进一步发展您的空间/thunk 直觉。另请参阅下面 Edward Kmett 的评论。

【讨论】:

  • “随机扰动优化”是一个很棒的表达方式。
  • 我觉得iterate'的类型签名应该是这样的:iterate' :: (a -> a) -> a -> [a]
  • @Sibi,感谢您的更正。你不强制第一个元素是对的——但这并不重要。 0 thunks deep,1 thunk deep,关键是我们不会退化到 1000。
  • @user3570838,您在严格世界中的设计原则在非严格世界中可能不会成为很好的模式——这是一个完全不同的地方。我已经说过如何在非严格世界中进行设计的最佳建议:尽量尊重您的类型的数据/余数据模式。我真的不知道除了练习还能说什么——也许其他人有一些更具体的建议。您可能想查看conal.net/papers/type-class-morphisms 以了解一般设计原则(而不是重新:效率),除了可以使用您的严格模式之外,还可以为您提供一些东西。
  • 我通常的经验法则是尽量确保无论它是什么,如果它可以积累无限的 thunk 链,最好每隔一段时间就去那里清理一下。我不是在谈论deepseq。我说的是当你有像手指树这样的东西时,插入的进程最终会操纵树的下部分支,因此不会在下面积累。像大小这样的东西,通常在它下面的小调整不会强迫它,它只会积累 thunk,导致一个相当大的炸弹在等待任何使用它的人,所以它可以节省你
猜你喜欢
  • 2011-05-21
  • 2011-01-13
  • 2023-03-24
  • 1970-01-01
  • 2020-10-01
  • 2011-08-05
  • 1970-01-01
  • 1970-01-01
  • 2012-06-21
相关资源
最近更新 更多