seq在Haskell生产代码中多久使用一次?


23

我在使用Haskell编写小型工具方面有一些经验,我发现使用起来非常直观,尤其是对于编写过滤器(使用interact)来处理其标准输入并将其通过管道传输到标准输出。

最近,我尝试在比通常大10倍的文件上使用这样的过滤器,但出现Stack space overflow错误。

在阅读了一些内容之后(例如,在这里这里),我确定了两个节省堆栈空间的准则(有经验的Haskellers,如果我写的东西不正确,请纠正我):

  1. 避免使用非尾递归的递归函数调用(这对所有支持尾调用优化的功能语言均有效)。
  2. 引入seq强制对子表达式进行早期评估,以使表达式在被缩减之前不会变得太大(这是Haskell所特有的,或者至少是使用惰性评估的语言所特有的)。

seq在我的代码中引入了五到六个调用之后,我的工具再次运行平稳(也适用于较大的数据)。但是,我发现原始代码更具可读性。

由于我不是一位经验丰富的Haskell程序员,所以我想问一下seq以这种方式进行介绍是否是一种常见的做法,以及通常seq在Haskell生产代码中看到该代码的频率。还是有什么技术可以避免使用seq太频繁而仍然只使用很少的堆栈空间?


1
像您所描述的类型之类的优化几乎总是会使代码变得不太优雅。
罗伯特·哈维

@Robert Harvey:是否有其他技术可以使堆栈使用率保持较低?我的意思是我想我必须以不同的方式重写我的函数,但是我不知道是否存在完善的技术。我的第一个尝试是使用尾递归函数,该函数虽然有帮助,但却无法完全解决我的问题。
乔治

Answers:


17

不幸的是,在某些情况下,必须使用seq某个程序才能获得高效/运行良好的大数据程序。因此,在很多情况下,您无法在生产代码中做到这一点。您可以在《 Real World Haskell》第25章“概要分析和优化”中找到更多信息。

但是,有可能避免seq直接使用。这可以使代码更简洁,更健壮。一些想法:

  1. 使用导管管道迭代器代替interact。众所周知,惰性IO在管理资源(而不仅仅是内存)方面存在问题,迭代器的设计正是为了解决这一问题。(无论您的数据有多大,我建议都避免一起使用惰性IO-请参阅惰性I / O的问题。)
  2. 而不是seq直接使用(或设计自己的)组合器(例如foldl'foldr')或为严格计算而设计的严格版本的库(例如Data.Map.StrictControl.Monad.State.Strict)。
  3. 使用BangPatterns扩展名。它允许替换seq为严格的模式匹配。在某些情况下,声明严格的构造函数字段也可能很有用。
  4. 也可以使用策略来强制评估。策略库主要针对并行计算,但也具有将值强制为WHNFrseq)或完整NFrdeepseq)的方法。有很多实用的方法可以处理集合,组合策略等。

+1:感谢您的有用提示和链接。第三点似乎很有趣(这是我现在可以使用的最简单的解决方案)。关于建议1,我看不到避免惰性IO可以如何改善事情:据我了解,对于应该处理(可能很长)数据流的过滤器,惰性IO应该更好。
乔治

2
@Giorgio我向Haskell Wiki添加了有关Lazy IO问题的链接。使用惰性IO,您将很难管理资源。例如,如果您没有完全读取输入(例如由于延迟求值),则文件句柄将保持打开状态。而且,如果您手动关闭文件句柄,则经常会发生这种情况:由于懒惰的评估读取而将其推迟,因此您在读取整个输入之前先关闭句柄。而且,通常很难避免使用惰性IO导致的内存问题。
PetrPudlák2012年

我最近遇到了这个问题,并且我的程序用尽了文件描述符。因此,我使用strict将懒惰的IO替换为严格的IO ByteString
乔治
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.