【问题标题】:Haskell's `seq` evaluates arguments redundantly? [duplicate]Haskell 的`seq` 冗余地评估参数? [复制]
【发布时间】:2018-10-29 14:30:16
【问题描述】:

如果我正确理解了here 的讨论,seq 不应该对一个值进行两次评估,因为x `seq` x 应该对x 进行一次评估。

那我为什么会有这种行为呢?

λ> :set +s
λ> let fib x = if x <= 1 then x else fib (x - 1) + fib (x - 2)
(0.01 secs, 102,600 bytes)
λ> fib 30
832040
(2.49 secs, 638,088,448 bytes)
λ> let x = fib 30 in x
832040
(2.47 secs, 638,088,792 bytes)
λ> let x = fib 30 in x `seq` x
832040
(4.95 secs, 1,276,067,128 bytes)

这显然是双重评估?我是不是误会了什么?

编辑:正如下面@danidiaz 所问的,我也评估了

λ> (\x -> x `seq` x) (fib 30)
832040
(2.51 secs, 638,087,888 bytes)
λ> let x = (fib 30) :: Int in x `seq` x
832040
(2.52 secs, 732,476,640 bytes)

现在更令人惊讶。

编辑 2: 我看到这个问题已被标记为之前询问单态限制的问题的副本。当我遇到这个问题时,我不知道这是由于限制。因此,如果有人发现他/她自己处于我的位置,我想这个问题的答案会有所帮助。

【问题讨论】:

  • 如果你尝试(\x -&gt; x `seq` x) (fib 30)let x = (fib 30) :: Int in x `seq` x 会发生什么?
  • 已添加到帖子中,现在更加惊讶!
  • 问题是fib 具有多态类型(Ord a, Num a) =&gt; a -&gt; a。因此,在表达式x `seq` x 中,第一个x 可以实例化为与第二个x 不同的单态类型。例如,第一个x 可以是Int,而第二个x 可以是Double。没有什么可以迫使两者成为同一类型。这就是为什么 x 被评估两次的原因。
  • 解决方法很简单。给fib 一个明确的类型签名,如let fib :: Int -&gt; Int; fib x = if x &lt;= 1 then x else fib (x - 1) + fib (x - 2)
  • 避免这种性能问题正是引入单态限制的原因。没有 MR,x 是多态的,x `seq` x 实际上意味着类似于x () `seq` x (),好像x 是一个函数,而seq 将调用该函数两次(可以理解)。

标签: performance haskell


【解决方案1】:

对于这个答案的第一部分,:set -XNoMonomorphismRestriction in ghci。后面会解释。

天真地,人们会期望在 Haskell 中 let x = 5 in (x + 1,x + 2) 总是等价于 (\x -&gt; (x + 1, x + 2)) 5。但它们有不同的类型!

let x = 5 in (x + 1,x + 2) :: (Num a, Num b) => (a, b)

(\x -> (x + 1,x + 2)) 5 :: Num b => (b, b)

原因是 Haskell 的一个名为 let-bound polymorphism 的特性。与 lambda 绑定标识符不同,绑定在 let 中的标识符可以在 let 的主体中以不同方式实例化。例如:

ghci> let f = id in (f True, f 'a')
(True,'a')

ghci> (\f -> (f True, f 'a')) id
*** ERROR ***

现在,您没有为 fib 函数提供类型签名,而推断出来的类型类似于

fib :: (Ord a, Num a) => a -> a

这将适用于 Num 的不同实例,例如 IntFloat 等。

但是正因为如此,当你写x `seq` x时,ghci不能确定这两个x实际上是同一类型的!如果它们可能不同,则无法共享。

这就是(\x -&gt; x `seq` x) (fib 30) 有分享功能的原因。因为x 是 lambda-bound,所以编译器确信这两个事件确实是相同的值。 let x = (fib 30) :: Int in x `seq` x 也一样,因为我们使用显式类型移除了多态性。

还有另一种出路。打开-XMonomorphismRestriction 扩展会增加类型默认的数量,导致let 表达式比人们预期的更加单态。在这种情况下,这也应该足以恢复共享。

【讨论】:

    猜你喜欢
    • 2016-06-28
    • 1970-01-01
    • 2013-01-20
    • 2019-07-21
    • 1970-01-01
    • 1970-01-01
    • 2020-06-17
    • 1970-01-01
    • 2012-09-13
    相关资源
    最近更新 更多