Lisp 中`do` 和`do*` 的区别?

pmg*_*pmg 1 lisp common-lisp undefined-behavior

我的工作,只有在不同的如何这两个功能retcurr分配,而循环运行自己的价值。在第一个函数中retcurr是并行绑定的;在第二个函数中,它们按顺序绑定

平行绑定

(defun maxpower (base maximum)
    "returns base ^ k such that it is <= maximum"
    (do ((ret 1 curr)               ; parallel
         (curr base (* base curr))) ; binding
        ((> curr maximum) ret)))
Run Code Online (Sandbox Code Playgroud)

顺序绑定

(defun maxpower* (base maximum)
    "returns base ^ k such that it is <= maximum"
    (do* ((ret 1 curr)                ; sequential
          (curr base (* base curr)))  ; binding
         ((> curr maximum) ret)))
Run Code Online (Sandbox Code Playgroud)

问题:第一个函数是否有某种错误 (*),因为curr它同时(并行)更新和评估?

IOW:如果我更改绑定的顺序,并行版本应该没有区别吗?
Lisp 如何决定绑定的并行化?

在我的测试中,两个函数都返回相同的值。

(*):我来自C背景;我会说第一个函数调用未定义的行为。

Kaz*_*Kaz 5

这也可能是最好看letlet*第一。如果您理解这一点,那么除了对阶梯形式的额外考虑之外,dovsdo*随之而来。

Common Lisp 是一种严格评估的语言。在let和 中let*,变量 init-forms 从左到右求值。区别在于范围和绑定。在 下let,所有init表单都在一个变量都不可见的范围内进行评估,而在 下let*,表单在所有先前变量都可见的环境中进行评估。其次,由于在 下let*,前面的变量是可见的,它们的值也成立。

使用let我们可以创建一个范围,其中两个变量的值出现交换:

(let ((x y)
      (y x))
   ...)
Run Code Online (Sandbox Code Playgroud)

初始化表达式yandx会首先按此顺序求值x,然后是新值andy绑定到结果值,这使得这成为可能。

另一方面:

(let* ((a 1)
       (b (+ a 2)))
Run Code Online (Sandbox Code Playgroud)

在这里,1被评估,并被a绑定。a然后这对其(+ a 2)值被计算的表达式可见,并绑定到b

现在,到do/ do*。这些宏在第一次迭代之前执行与let/完全一样的变量绑定let*。在绑定变量之间的差异dodo*酷似之间letlet*

do/do*宏也有逐步形成,这给未来的价值,他们对应的迭代变量。这些步进形式都在所有变量的范围内,无论宏运算符是do还是do*。无论您使用dodo*,您都可以以任何步骤形式引用任何变量。不同之处在于分配发生的时间。在 下do,从上到下评估所有步骤形式,然后为下一次迭代为其相应的变量分配新值。在 下do*,行为是“随你分配”。在评估每个步骤形式时,会分配相应的变量。因此do,在下,当 step-form 指代任何变量时,它指的是它在先前迭代中的值。在下面do*, 如果 step-form 引用词法上较早的变量,则它正在获取新值。如果它引用词法上较晚的变量,它仍然会看到先前迭代中的旧值。

我们要强调的是,虽然letdo有一些“平行”的行为,但从某种意义上说,并没有平行的评价。所有可见的效果都是从左到右执行的。似乎并行发生的是变量的出现,或者在新的迭代中被分配了新的值。但这只是在程序无法观察中间进度的意义上并行。例如,将函数参数传递到函数中同样是“并行的”;程序不会观察到函数调用部分正在进行的状态,并且只传递了一半的参数。