这个 Scheme 如何编码 return 一个值?

How does this Scheme code return a value?

这段代码取自Sussman和Wisdom的经典力学的结构与解释,其目的是导出(接近)宿主机支持的最小正浮点数。 https://github.com/hnarayanan/sicm/blob/e37f011db68f8efc51ae309cd61bf497b90970da/scmutils/src/kernel/numeric.scm

运行 它在 DrRacket 中的结果是 2.220446049250313e-016 在我的机器上。

我的问题是,是什么导致这个甚至 return 一个值?这段代码是尾递归的,在某些时候计算机不能再除以2是有道理的。为什么不抛出?

(define *machine-epsilon*
  (let loop ((e 1.0))
     (if (= 1.0 (+ e 1.0))
         (* 2 e)
         (loop (/ e 2)))))
*machine-epsilon*

This code is tail recursive, and it makes sense at some point the computer can no longer divide by 2. Why does it not throw?

不,想法不同:在某些时候计算机仍然可以 除以 2,但结果 (e) 变得与 0 无法区分 [upd: 仅在浮点加法的上下文中 - 评论中提到的非常好的观点](e + 1.0 = 1.0,这正是 if 子句正在检查的内容)。我们肯定知道之前的e仍然大于零"from the machine point of view"(否则我们不会到达当前执行点),所以我们简单地return e*2

当你摆脱令人困惑的命名 let 符号时,它会变得更加清晰。

(define (calculate-epsilon (epsilon 1.0))
  (if (= 1.0 (+ 1.0 epsilon))
      (* epsilon 2)
      (calculate-epsilon (/ epsilon 2))))

(define *machine-epsilon* (calculate-epsilon))

是代码的实际作用。 所以现在我们看看命名 let 表达式的好处。 它在本地定义函数并运行它。只是函数的名称 loop 非常不精确且令人困惑,而将 epsilon 命名为 e 是一个非常不愉快的选择。命名对于可读代码来说是最重要的事情。

所以这个 SICP 的例子应该是错误命名选择的一个例子。 (好吧,也许他们是故意训练学生的)。

命名的 let 定义 calls/runs 一个 function/procedure。避免它会导致更好的代码 - 因为更清晰。

在普通的 lisp 中,这样的构造会表达得更清楚:

(defparameter *machine-epsilon*
  (labels ((calculate-epsilon (&optional (epsilon 1.0))
              (if (= 1.0 (+ 1.0 epsilon))
                  (* epsilon 2)
                  (calculate-epsilon (/ epsilon 2)))))
    (calculate-epsilon)))

在 CLISP 实现中,这给出:1.1920929E-7

这个form of let-binding是递归的语法糖。

在你掌握这门语言之前,你可能会避免使用过多的语法,并尽可能使用内核语言来编写,以专注于本质问题。例如,在完整的 SICP 文本中,从来没有为迭代指定这种语法糖。

迭代的 r6rs 定义是 here

这段代码的目的是不是找机器能支持的最小浮点数:是找最小的浮点数,epsilon使得(= (+ 1.0 epsilon) 1.0) 是错误的。这个数字很有用,因为它是你从数字相加得到的错误的上限特别是你知道的是,比方说,(+ x y) 在 [(x+y)*(1 - epsilon) 范围内,( x+y)*(1 + epsilon)],其中第二个表达式 + &c 表示对数字的理想运算。

特别是 (/ *machine-epsilon* 2) 是一个非常好的数字,例如 (/ *machine-epsilon* 10000)(* (/ *machine-epsilon* x) x) 将非常接近 *machine-epsilon* 对于许多合理的 x。只是(= (+ (/ *machine-epsilon* 2) 1.0) 1.0)是真的

我对浮点标准还不够熟悉,但您可能想到的数字是 Common Lisp 所说的 least-positive-double-float(或其变体)。在 Racket 中,您可以通过

得出一些近似值
(define *least-positive-mumble-float*
  ;; I don't know what float types Racket has if it even has more than one.
  (let loop ([t 1.0])
    (if (= (/ t 2) 0.0)
        t
        (loop (/ t 2)))))

我不确定这是否允许引发异常:它在实践中并没有得到一个看起来合理的答案。