我正在研究这两个函数,它们的区别仅在于循环运行时如何为ret
和curr
赋值。在第一个函数中,ret
和curr
并行绑定 ;在第二个函数中,它们依次绑定。
平行绑定
(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背景;我想说第一个函数调用未定义的行为。
答案 0 :(得分:5)
最好先看看let
与let*
。如果您了解这一点,那么do
与do*
的区别就在于此,除了另外考虑步骤形式。
Common Lisp是一种经过严格评估的语言。在let
和let*
中,变量init-forms从左到右求值。区别在于范围和绑定。在let
下,所有 init 表单都在一个变量不可见的范围内进行评估,而在let*
下,这些表单在以下环境下进行评估:所有先前的变量都是可见的。其次,由于在let*
下,前面的变量是可见的,因此它们的值也得以建立。
使用let
,我们可以创建一个范围,其中两个变量的值看起来会互换:
(let ((x y)
(y x))
...)
首先按该顺序求值初始化表达式y
和x
,然后将新值x
和y
绑定到结果值,这使得这可能。
另一方面:
(let* ((a 1)
(b (+ a 2)))
在这里,1
被求值,并且a
被绑定。然后,该a
对计算其值的(+ a 2)
表达式可见,并绑定到b
。
现在,转到do
/ do*
上。这些宏在第一次迭代之前执行与let
/ let*
完全一样的变量绑定。在绑定变量时,do
和do*
之间的差异与let
和let*
之间的差异完全相同。
do
/ do*
宏还具有步进形式,这些阶跃形式将下一个值赋予其对应的迭代变量。这些步进形式都在所有变量的范围内,无论宏运算符是do
还是do*
。无论您使用的是do
还是do*
,都可以以任何步骤形式引用任何变量。区别在于分配发生的时间。在do
下,从上到下评估所有步骤形式,然后为它们的相应变量分配下一个迭代的新值。在do*
下,该行为是“随您分配”。在评估每个步骤形式时,将分配相应的变量。因此,在do
下,当步进形式引用任何变量时,它引用的是先前迭代中的值。在do*
下,如果步骤表引用的是词法更早的变量,则它将采用新值。如果它引用的是词法上较晚的变量,则仍会看到先前迭代中的旧值。
我们必须强调,尽管let
和do
具有某些“并行”行为,但在某种意义上讲,没有并行评估。所有可见效果都是从左到右执行的。似乎并行发生的是变量出现或在新迭代中被赋予新值。但这只是在程序无法观察到中间进度的意义上是并行的。例如,将函数参数传递到函数中的过程同样是“并行”的。该程序不会观察到部分函数调用正在进行且仅传递了一半参数的状态。
答案 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
,并并行更新curr
和ret
的值。但是,对于第二个函数,将评估(* 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