【问题标题】:Stack overflow during evaluation in OCamlOCaml 评估期间的堆栈溢出
【发布时间】:2017-09-19 06:01:06
【问题描述】:

在实现 Heron of Alexandria 的平方根近似算法时遇到堆栈溢出问题,如下所示:

我们从平方根为 1.0 的初始(差)近似答案开始,然后继续改进猜测,直到我们在真实答案的 delta 范围内。通过使用 x/guess 对当前猜测进行平均来实现改进。答案精确到 delta = 0.0001 以内。

我的实现尝试如下:

let squareRoot (x : float) : float =
  let rec aux input guess =
    if abs_float(guess**2. -. input) < 0.0001 then guess
    else aux input (guess +. input/.guess)/.2. in
  aux x 1.;;

但是,这会在 OCaml REPL 中引发 # Stack overflow during evaluation (looping recursion?). 错误。我尝试在 Python 中实现一个相同的算法,如下所示:

def squareRoot(x):
  def aux (s, guess):
    if abs(pow(guess,2) - s) < 0.0001:
      return guess
    else:
      return aux (s, (guess + s/guess)/2)
  return aux (x, 1)

...运行得很好。所以我玩弄了 OCaml 代码,并将我最​​初的尝试改为:

let squareRoot (x : float) : float =
  let improve i g = (g +. i/.g)/.2. in
  let rec aux input guess =
    if abs_float(guess ** 2. -. input) < 0.0001 then guess
    else aux input (improve input guess) in
  aux x 1.;;

我所做的只是将算法的改进部分包装在一个单独的函数中,但现在代码运行成功,没有任何堆栈溢出错误!

如果有人能解释为什么会这样,我将不胜感激,OCaml REPL/编译器背后的机制可能在我的第一次代码迭代中无法识别递归调用中的终止条件等。

【问题讨论】:

  • 您可以添加一些调试 Printf.eprintf 或使用 Ocaml 调试器
  • @BasileStarynkevitch 感谢您的建议!我只在 OCaml 上玩了几天,如果这个问题太琐碎了,请见谅。

标签: recursion ocaml stack-overflow


【解决方案1】:
aux input (guess +. input/.guess)/.2. 

aux 的应用发生在 2. 的除法之前...)

被解析为

  (aux input (guess +. input/.guess))/.2

你真的想要

  aux input ((guess +. input/.guess)/.2.)

甚至(阅读A-normal forms)

  let newguess = (guess +. input/.guess)/.2. 
  in
      aux input newguess

(可能更易读,有些人使用guess'之类的名称)

顺便说一句,有些人会编码

  let guess =  aux input ((guess +. input/.guess)/.2.)
  in aux input guess

(没有递归,而是lexical scoping

但我不喜欢这样的编码(重复使用 same 名称guess

根据经验,不要害羞地使用括号(或begin ... end,这是相同的)和中间let 绑定。两者都使您的代码更具可读性。

【讨论】:

  • 这是一个简单的语义错误,在这里我以为有一些更大的背景故事... /facepalm 非常感谢!
猜你喜欢
  • 1970-01-01
  • 2011-11-24
  • 2017-09-29
  • 1970-01-01
  • 2016-09-15
  • 1970-01-01
  • 1970-01-01
  • 2019-05-28
  • 1970-01-01
相关资源
最近更新 更多