这个 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)))))
我不确定这是否允许引发异常:它在实践中并没有得到一个看起来合理的答案。
这段代码取自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)))))
我不确定这是否允许引发异常:它在实践中并没有得到一个看起来合理的答案。