为什么每次我试图获取它的值时都会调用这个函数?

Why is this function being called every time I try to get its value?

我有这个 Haskell 代码,我用 π/4 = 1 - 1/3 + 1/5 - 1/7 + 1/9...:

系列计算圆周率
quarterPi total n s = if (n /= total) then s / (2 * n - 1) + quarterPi total (n + 1) (-s) else s / (2 * n - 1)

calcPi n = 4 * quarterPi n 1 1

我 运行 使用 GHCi 前奏的代码并尝试将 pi 的近似值存储在变量中;类似于:*Main> pi = calcPi 666666,这显然会在完成计算之前进行许多次递归迭代。我知道第一次检索 pi 的值时,由于惰性求值,计算需要时间。但是由于某种原因,它总是需要很长时间;无论我使用该值多少次或在屏幕上输出该值多少次,都需要几秒钟才能产生结果。惰性求值不应该只计算一次吗?

GHCi 没有优化到足以弄清楚它不应该在每次打印时重新评估它。如果您使用通常的 -O2 标志编译,普通 GHC 将正确缓存结果。

Try it online 并将总 运行 时间与其打印的次数进行比较 "Pi:3.14..."

这里困扰您的是所谓的单态限制——但不寻常的是,缺乏单态限制,而不是它的存在。事实证明 pi 并不像您预期​​的那样恒定,因为它的类型比您可能意识到的更通用。


如果我们在 REPL 中打开分析信息:

*Main> :set +s

然后我们可以看到 pi 每次都重新计算,就像你说的那样。

*Main> pi = calcPi 666666
(0.00 secs, 0 bytes)
*Main> pi
3.141591153588293
(0.94 secs, 551,350,552 bytes)
*Main> pi
3.141591153588293
(0.99 secs, 551,300,808 bytes)

这是因为pi类型被推断为class类型多态:

*Main> :t pi
pi :: (Fractional a, Eq a) => a

所以pi在某种意义上是"not really a constant";有很多 pis!有 pi :: Doublepi :: Floatpi :: Complex Double,等等。而且GHC不缓存函数应用,所以pi每次都重新计算。

在 Haskell files 中,单态限制生效:变量绑定,如 x = …,永远不会被推断为具有类型class 像 C a => … 这样的多态类型。如果可能的话,这样的定义是默认的——一个过程,其中Haskell使值在标准数字类型中是多态的classes在Integer或[=28处是单态的=].这就是为什么你可以写 x^2 而不会出现 "ambiguous type variable" 错误(即使指数的类型不影响输出的类型); 2 被默默地推断为 Integer.

类型

但是单态限制只适用于文件内部。在 GHCi 中,它被禁用。如果我们重新打开单态限制:

*Main> :set -XMonomorphismRestriction

然后我们可以定义pi',我们只需要计算一次。

*Main> pi' = calcPi 666666
(0.00 secs, 0 bytes)
*Main> pi'
3.141591153588293
(0.87 secs, 551,303,896 bytes)
*Main> pi'
3.141591153588293
(0.00 secs, 315,168 bytes)

那是因为 pi' 确实是一个常数。

*Main> :t pi'
pi' :: Double

补充几点:

  1. 懒惰与为什么第二次计算 pi' 很快无关;这在严格的语言中也是如此。懒惰是我们定义 pi/pi' 的行报告“(0.00 秒,0 字节)”的原因。

  2. 我不清楚你是否认为它可能,但是 Haskell 将 永远不会 缓存你正在进行的递归调用。 calcPi 666666 之类的东西总是会进行所有 666,666 次递归调用。只有将其显式保存在变量中后才会停止重新计算。

  3. 单态限制和类型默认在 Haskell 2010 年报告中用技术语言进行了解释:§4.4.5 "The Monomorphism Restriction, and §4.3.4 "Ambiguous Types, and Defaults for Overloaded Numeric Operations". The GHC User's Guide mentions that the monomorphism restriction is disabled in GHCi, and mentions how to re-/dis-enable it: §9.17.1 "Switching off the dreaded Monomorphism Restriction"