【问题标题】:Difference between `do` and `do*` in Lisp?Lisp中`do`和`do*`之间的区别?
【发布时间】:2020-06-20 20:46:01
【问题描述】:

我正在研究这两个函数,它们的区别仅在于循环运行时 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 被同时更新和评估(并行)?

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

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

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

【问题讨论】:

    标签: lisp common-lisp undefined-behavior


    【解决方案1】:

    最好先看看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 下,当step-form 引用任何变量时,它引用的是它在之前迭代中的值。在do* 下,如果 step-form 引用了一个词法上较早的变量,它会获取新值。如果它引用词法上较晚的变量,它仍然会看到先前迭代的旧值。

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

    【讨论】:

      【解决方案2】:

      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然后curr 被评估并分配给ret

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

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

      【讨论】:

        猜你喜欢
        • 2011-04-07
        • 1970-01-01
        • 2016-04-01
        • 2015-04-30
        • 2021-09-22
        • 2010-11-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多