Lisp中的do和do *之间的区别?

时间:2020-06-20 20:46:01

标签: 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)))

顺序绑定

(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)))

问题:第一个函数是不是错误 (*),因为curr同时(并行)更新和求值?< / p>

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

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

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

2 个答案:

答案 0 :(得分:5)

最好先看看letlet*。如果您了解这一点,那么dodo*的区别就在于此,除了另外考虑步骤形式。

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

使用let,我们可以创建一个范围,其中两个变量的值看起来会互换:

(let ((x y)
      (y x))
   ...)

首先按该顺序求值初始化表达式yx,然后将新值xy绑定到结果值,这使得这可能。

另一方面:

(let* ((a 1)
       (b (+ a 2)))

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

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

do / do*宏还具有步进形式,这些阶跃形式将下一个值赋予其对应的迭代变量。这些步进形式都在所有变量的范围内,无论宏运算符是do还是do*。无论您使用的是do还是do*,都可以以任何步骤形式引用任何变量。区别在于分配发生的时间。在do下,从上到下评估所有步骤形式,然后为它们的相应变量分配下一个迭代的新值。在do*下,该行为是“随您分配”。在评估每个步骤形式时,将分配相应的变量。因此,在do下,当步进形式引用任何变量时,它引用的是先前迭代中的值。在do*下,如果步骤表引用的是词法更早的变量,则它将采用新值。如果它引用的是词法上较晚的变量,则仍会看到先前迭代中的旧值。

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

答案 1 :(得分:4)

对于maxpower,“ curr同时更新和评估”是不正确的。 do中的步骤表单在进行任何赋值之前都已评估。对于do,Hyperspec说“ the assignment of values to vars is done in parallel, as if by psetq”,对于psetq,它说"first all of the forms are evaluated, and only then are the variables set to the resulting values."

在发布的代码中,两个定义应产生相同的结果,因为在完成任何赋值之前先评估步骤表单。但是,如果绑定的顺序颠倒了,事情就会有所不同:

(defun maxpower (base maximum)
  (do ((curr base (* base curr))
       (ret 1 curr))
      ((> curr maximum) ret)))

(defun maxpower* (base maximum)
  (do* ((curr base (* base curr))
        (ret 1 curr))
       ((> curr maximum) ret)))

现在对于第一个函数,同时评估(* base curr)curr,并并行更新currret的值。但是,对于第二个函数,将评估(* base curr)并将结果分配给curr,然后评估 then curr并将其分配给ret

对于这些新定义,您会看到结果不同,在原始定义中,两个函数对于(maxpower 2 5)(maxpower* 2 5)都将返回4:

CL-USER> (maxpower 2 5)
4
CL-USER> (maxpower* 2 5)
8