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

标签 haskell recursion pi

我编写了这个 Haskell 代码,用于使用系列来计算 pi π/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 prelude 运行代码,并尝试将 pi 的近似值存储在变量中;类似:*Main> pi = calcPi 666666,这显然需要多次递归迭代才能完成计算。据我了解,第一次检索 pi 的值时,由于惰性求值,需要一些时间来计算。但出于某种原因,这总是需要很长时间;无论我使用该值多少次或在屏幕上输出该值多少次,都需要几秒钟才能产生结果。由于惰性评估,它不应该只需要计算一次吗?

最佳答案

这里困扰你的是所谓的单态限制 - 但不同寻常的是,它缺乏单态限制,而不是它的存在。事实证明,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类型被推断为类型类多态:

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

所以 pi 在某种意义上“并不是真正的常数”;有很多 pi!有 pi::Doublepi::Floatpi::Complex Double 等等。而且GHC不会缓存函数应用程序,因此每次都会重新计算pi

然而,在 Haskell 文件中,单态限制是有效的:变量绑定(bind),如 x = ...,永远不会被推断为具有类型类多态类型,如C a => ...。如果可能的话,这样的定义是默认的——Haskell在这个过程中使标准数字类型类中的值在IntegerDouble单态。这就是为什么您可以编写 x^2 而不会出现“模糊类型变量”错误(即使指数的类型不影响输出的类型); 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 secs, 0 bytes)”的原因。

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

  3. Haskell 2010 报告中用技术语言解释了单态限制和类型默认:§4.4.5 "The Monomorphism Restriction ,和§4.3.4 "Ambiguous Types, and Defaults for Overloaded Numeric Operations" 。 GHC 用户指南提到 GHCi 中禁用了单态限制,并提到了如何重新/禁用它:§9.17.1 "Switching off the dreaded Monomorphism Restriction"

关于haskell - 为什么每次我尝试获取它的值时都会调用这个函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42847682/

相关文章:

compiler-construction - 为什么用函数式语言编写编译器更容易?

haskell - 使用类型来防止列表中的端口号冲突

c++ - 求Pi第n位的函数

c# - 混合扩展方法、泛型和 lambda 表达式

memory - 循环函数意外使用内存

ruby - 在 Ruby 中递归列出目录的一行代码?

java - 包含 "bab"的 boolean 函数

haskell - 交错功能

java - 用 Java 计算 Pi

python - 无法导入名称 '_gi'