Haskell 严格的数据定义

Haskell 严格的数据定义,haskell,strictness,Haskell,Strictness,我看过很多对话/阅读的博客文章,你应该在数据中有严格的字段,以避免各种性能问题,例如: data Person = Person { personName :: !Text , personBirthday :: !UTCTime } 这对我来说完全有道理。由于对该数据的操作函数是惰性的,所以不会牺牲可组合性 但是,如果我添加一个字段,则可能字段: data Person = Person { personName :: !Text ,

我看过很多对话/阅读的博客文章,你应该在
数据中有严格的字段,以避免各种性能问题,例如:

data Person = Person
    { personName     :: !Text
    , personBirthday :: !UTCTime
    }
这对我来说完全有道理。由于对该数据的操作函数是惰性的,所以不会牺牲可组合性

但是,如果我添加一个
字段,则可能
字段:

data Person = Person
    { personName     :: !Text
    , personBirthday :: !UTCTime
    , personAddress  :: !(Maybe Address)
    }
我将懒散引入到数据结构中,毕竟
可能是一种控制结构。不能将未计算的thunk隐藏在
构造函数后面吗

然而,在或通过。但是根据反向依赖(,)的说法,它们并没有被广泛使用


所以问题是:为什么在非控制数据定义中应该或不应该使用strict
Maybe

  • 如果您分析代码并注意到空间泄漏,这可能是堵塞泄漏的方法

  • 严格的数据类型被广泛认为是高性能代码的有用工具

  • 其他人也在这么做(那些严格的软件包可能不受欢迎,但它们的存在是有原因的——你也可以说,你需要这些严格软件包的应用程序可能不会上传到Hackage)

理由不包括:

  • 不是在序曲中,所以它是一个额外的包

  • 您可能没有编写高性能代码

  • 您的程序可能不会因为将爆炸向内推一级而变得更快

  • 如果您正在编写高性能代码,您可以

总的来说,我不认为教条会这样或那样。这只是一个方便的问题。

这并不像“严格是快,懒惰是慢”那么简单 懒惰和严格都有助于提高绩效;让我们来看看懒惰是怎样的:

  • 显然,
    sum$take 10$[1..]
    如果列表是严格的,则需要无限的时间和无限的内存;如果列表是惰性的,则需要有限的时间和恒定的内存

  • 函数数据结构通常不允许良好的摊销界限。“摊销”界限是什么?当你只在O(n)个完全不相关的步骤之后才支付O(f(n))的成本时,我们可以想象地将其重新定义为为为每个步骤支付O(f(n)/n:因此,如果你向列表中添加n个元素,然后在n个logn时间内对其排序一次,那么你可以重新定义为每次添加都需要logn时间。(如果你用一个自平衡的二叉搜索树而不是一个列表来支持它,它就会这样做,但我们可以说,即使有一个列表,成本也是对数n摊销的。)

    将其与函数式编程相结合的问题是,有一个普遍的承诺,即当我给你一个新的数据结构时,旧的数据结构不会被修改,因此作为一般理论的观点,如果转换某些X需要花费很多成本,那么就存在一个有效的使用模式,它像以前一样花费n个精力构建X,但随后以不同的方式使用它m次(因为它没有被修改!),每次都会产生O(f(n))成本:所以现在当你尝试摊销时,你只得到O(m f(n)/n,如果m与n成比例,您已经从对整个结构产生一次成本,转变为对数据结构的每次添加基本上产生一次成本。在创建数据结构时,不能说“哦,那不是我的用例”:即使它不是你的,说到底也可能是别人的

    Okasaki指出(在中)惰性(通过记忆)实际上正是我们需要填补这一差距的原因:假设X将其后处理版本存储为惰性值,那么对transform X的每次调用都将命中相同的惰性值并产生相同的记忆答案。所以:如果你能巧妙地将这些东西移到thunks中,那么Haskell不重新计算thunks的事实就可以用来进行记忆化的论证

  • 例如,Haskell中的
    ++
    是一个O(1)操作;对于严格列表,将大小为n的列表附加到大小为m的列表的末尾需要预先分配O(m)内存,因为前面的列表需要完全重建;流串联将此转换为O(m)条件操作(幸运的是,在处理器中使用分支预测器时,这非常好!),并在每次读取列表时分配此开销

  • 若你们并没有使用一堆数据,但你们不知道自己在用或不在用什么东西,那个么懒惰是很有潜力的。举一个简单的例子,如果你不得不反复地反转一些昂贵的单调函数,而这些函数在某个有界区间上很难预测,那么你可能没有一个闭合的逆形式,或者没有一个函数或其导数的快速表达式(以便使用)。相反,您可以构建一个具有恒定深度的大型二叉搜索树,其节点用f(x)注释,其叶子表示x;然后,通过计算f(x)并对x进行二进制搜索,对一些输入x求逆f。然后,每个查询都会被自动记忆,因此,由于相同的缓存,搜索其他查询附近的值会得到渐进的加速(以不断增加内存的潜在成本为代价)

那么,什么时候严格有帮助呢? 您希望消除惰性的实际情况是递归数据结构,即使如此,这也只适用于您希望整个数据结构在内存中可用的情况(即,您将使用所有数据结构)。此类数据结构通常是严格的:例如,一个列表包含实际值的thunk,但指向其他列表节点的指针都是100%严格的

当这些条件都为真时,将惰性放在每个节点上就没有实际意义了,因为它为评估所有这些thunk提供了额外的O(n)成本,如果不使用严格性注释来抑制它,可能会将调用堆栈扩大到递归限制。如果你不是100%清楚的话,我看到的最好的解释是