【问题标题】:Lisp order of operations in lambda for mapcar用于 mapcar 的 lambda 中的 Lisp 操作顺序
【发布时间】:2014-01-03 20:09:42
【问题描述】:

我在 Linux 中运行 clisp,并在玩本书中的练习,ANSI Common Lisp。其中一个说要使用mapcar 创建一个函数,该函数接受一个整数列表并返回一个列表,其中每个元素都是原始元素加上它在列表中的索引。所以如果我执行(foo '(0 0 0)),它会产生(0 1 2),等等。

我尝试的是这样的:

(defun foo (lst)
  (let ((c 0))
    (mapcar #'(lambda (x) (+ x c) (incf c)) lst)))

当我运行(foo '(0 0 0)) 时,我得到的是(1 2 3)。作为一个实验,我交换了增量的顺序并以这种方式定义它:

(defun foo (lst)
  (let ((c 0))
    (mapcar #'(lambda (x) (incf c) (+ x c)) lst)))

我得到了完全相同的结果(在这种情况下,我希望(1 2 3),但不是在前一种情况下)。我也尝试像(progn (+ x c) (incf c)) 这样包装序列并得到相同的结果。为了使功能正常工作,我需要做(let ((c -1)))。为什么(incf c)(+ x c) 的顺序在这种情况下似乎无关紧要?无论如何,(incf c) 似乎总是先完成,但我不知道为什么。我知道我在这里遗漏了一些基本的东西,所以这应该是 LISP 专业人士的一个快速简单的回答,向我解释为什么会这样。 :)

谢谢。

【问题讨论】:

  • 没关系,我是个假人。 :-/ 感谢那些回答并帮助我看到它的人。

标签: lisp common-lisp


【解决方案1】:

在您的第一个示例中,lambda 函数的结果是最后一种形式的结果,(incf c)(+ x c) 被忽略。

在您的第二个示例中,您首先增加 c,因此第一个数字加 1,第二个数字加 2,依此类推。

对于您的第一个示例,您还可以使用 prog1 来返回 first 表单的值,而不是最后一个:

(defun foo (lst)
  (let ((c 0))
    (mapcar
     (lambda (e) (prog1 (+ e c) (incf c)))
     lst)))

【讨论】:

  • 哦,天哪,我知道这很愚蠢。谢谢。顺便说一句,你能解释一下你的替代方案吗?我认为将lambda 传递给mapcar 的正确语法需要#'(lambda...。我还在学习。
  • @Vatine 是的,谢谢。这又是我的一个大脑放屁。我现在看得很清楚了。
  • 再次感谢 uselpa,我了解您对 prog1 的使用,因为我已经对其进行了更多检查。
  • 我认为一个更简单的解决方案是使用(+ e (incf c)),然后在-1 开始c 而不是0
【解决方案2】:

注意:本文旨在探索实现这一目标的一些方法。您在问题中提到了其中一些解决方案(例如,从 -1 开始),我并不是说将它们包括在这里以表明您没有考虑过它们。这个答案更多地是关于考虑前增量和后增量运算符在面向值的语言中的作用。

请注意,(incf c) 不仅会产生副作用(顺序很重要),还会返回 c 的新值。也就是说,incf 是一个前置自增运算符。这意味着写(+ x (incf c)) 是绝对可以接受的,例如:

(defun foo (lst)
  (let ((c 0))
    (mapcar #'(lambda (x)
                (+ x (incf c)))
            lst)))

(foo '(0 0 0))
;=> (1 2 3)

现在,这些结果并不可怕,但它们不会将元素的索引添加到元素中,因为索引从零开始,而不是一。您可以做一个简单的更改并添加减法:

(defun foo (lst)
  (let ((c 0))
    (mapcar #'(lambda (x)
                (+ x (1- (incf c)))) ; or (+ x -1 (incf c)), etc.
            lst)))

(foo '(0 0 0))
;=> (0 1 2)

在我看来,甚至比这更好的是使用第一种形式,并在 -1 开始 c。这就是我可能的事情,无论如何,你在你的问题中指出这是一个选项:

(defun foo (lst)
  (let ((c -1))
    (mapcar #'(lambda (x)
                (+ x (incf c)))
            lst)))

(foo '(0 0 0))
;=> (0 1 2)

不过,我们可能会问,是否有一种方法可以逼近后自增运算符,这样我们就不必(在我们的代码中)看到减法?也就是说,我们可能会将上述内容视为某些语言中的内容:

result.add( x + ++c )

并问为什么我们不能这样做

result.add( x + c++ ) 

相反?这很简单:

(defmacro post-incf (place)
  `(1- (incf ,place)))

(defun foo (lst)
  (let ((c 0))
    (mapcar #'(lambda (x)
                (+ x (post-incf c)))
            lst)))

(foo '(0 0 0))
;=> (0 1 2)

实际上,类 C 语言的前置和后置自增运算符没有灵活性,但 Common Lisp 中的 incf and decf 通过使用可选参数提供了更多功能,其中可以使用第二个可选参数通过多少来指定值应该改变。调整post-incf 来处理这样的第二个参数并不难。第一次尝试可能是:

(defmacro post-incf (place &optional (delta-form 1))
  `(- (incf ,place ,delta-form) ,delta-form))

这对于诸如1 之类的常量值可以正常工作,但它会多次评估delta-form,我们不希望这样(因为它可能有副作用,或者计算成本很高)。我们只需要更加小心地评估一次,因此:

(defmacro post-incf (place &optional (delta-form 1))
  (let ((delta (gensym (symbol-name '#:delta-))))
    `(let ((,delta ,delta-form))
       (- (incf ,place ,delta) ,delta))))

(let ((a 0))
  (list a                ; => 0
        (incf a 3)       ; => 3
        (post-incf a 2)  ; => 3 (but x is now 5)
        a))              ; => 5
;=> (0 3 3 5)

【讨论】:

  • 感谢 Joshua 的详细说明,这很有帮助。
  • 虽然看到 incfdecf 机器人如何采用可选的增量值(默认为 1),但我更希望在 post-incf 中也有该选项。
【解决方案3】:

在您的第一个 lambda (lambda (x) (+ x c) (incf c)) 中,您正在执行一个无副作用的加法并丢弃它的值。然后增加 c 并返回新值,作为 lambda 的值。

尝试调用(foo '(10 10 10)),看看返回值是什么,两个版本都会返回(1 2 3)(11 12 13)

【讨论】:

  • 是的,谢谢 (+1)。对我来说是一个很大的“呃”。这很清楚。在所有 SO LISP 专业人士面前,我现在感觉自己像个笨蛋……:p
猜你喜欢
  • 2019-05-04
  • 1970-01-01
  • 2013-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-15
相关资源
最近更新 更多