注意:本文旨在探索实现这一目标的一些方法。您在问题中提到了其中一些解决方案(例如,从 -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)