Lisp参数指针

Lisp参数指针,lisp,common-lisp,Lisp,Common Lisp,我正在学习lisp,我必须从lisp中的函数返回修改后的输入参数 考虑这个简单的例子: (defun swap (l1 l2) (let ((temp)) (setf temp l1) (setf l1 l2) (setf l2 temp))) (setf a (list 1 2 3)) (setf b (list 7 8 9)) (swap a b) (print a) (print b) 它不起作用,因为我不知道如何将变量的引用传递给函数。这在lisp中可能吗

我正在学习lisp,我必须从lisp中的函数返回修改后的输入参数

考虑这个简单的例子:

(defun swap (l1 l2)
  (let ((temp))
    (setf temp l1)
    (setf l1 l2)
    (setf l2 temp)))

(setf a (list 1 2 3))
(setf b (list 7 8 9))
(swap a b)
(print a)
(print b)
它不起作用,因为我不知道如何将变量的引用传递给函数。这在lisp中可能吗如何解决此函数?


更新

;;; doesn't change original
(defun foo1 (x)
  (setf x (list 0 0 0)))

;;; but this does
(defun foo4 (x)
  (setf (car x) 0)
  (setf (cdr x) (list 0 0)))
我之所以希望通过引用传递一个变量来更改它,是因为当我有一个带有3个输入参数的函数,并且该函数应该更改所有这些参数时,我认为通过引用更改它们,然后返回三个变量的列表,然后用原始变量覆盖它们更为优雅:

;;; more elegant function
(defun foo (x y z)
  ;;... some work ...

  ;; Lets PRETEND this does work
  (setf x new-x)
  (setf y new-y)
  (setf z new-z))

; after this, a,b,c will have new values
(foo a b c)

;;; less elegant function
(defun foo (x y z)
  ;; ... some work ...
  (list new-x new-y new-z))

; after this, I still will have to manually set a,b,c
(setf temp (foo a b c))
(setf a (nth 0 tmp))
(setf b (nth 1 tmp))
(setf c (nth 2 tmp))
为了解释我为什么要完成这项任务,我有河内塔的家庭作业。我正在考虑使用三个列表作为
堆栈
,并在它们上使用
pop
push
功能插入和取出“光盘”。我定义了
(move n source target temp)
函数,它通过
n-1
change递归调用自己。问题是,当递归函数中的I
pop
push
堆栈时,它不会影响外部堆栈。 如果我想让我的
move
函数在
n
移动后返回堆栈,我真的应该返回新堆栈列表(不太优雅的函数),而不是通过引用编辑它们(更优雅的函数)


函数式语言中的正确方法是什么?

内置宏
rotatef
实现此功能:

(setf x 1)
(setf y 3)
;x = 1, y = 3
(rotatef x y)
;x = 3, y = 1
为了编写自己的函数来完成此操作,我建议创建:

但是,正如Clayton指出的,如果将该宏应用于名为“temp”的变量,则该宏将失败。因此,我们可以使用
gensym
创建一个新的变量名(保证不使用),并将其传递给一个辅助宏,该宏实际切换值:

(defmacro my-swap-impl (a b sym) ;;implementation of my-swap
          `(let ((,sym ,b)) ;evaluate the symbol and use it as a variable name
             (setf ,b ,a)
             (setf ,a ,sym)))
这是前一个swap宏的一个版本,它接受第三个参数作为临时变量名。这是从一个简单的宏调用的:

(defmacro my-swap (a b) ;;simply passes a variable name for use in my-swap-impl
          `(my-swap-impl ,a ,b ,(gensym)))

此设置与前一个设置完全相同,只是它可以安全地使用。

首先,如果您正在学习函数式编程或Lisp,而不仅仅是普通Lisp,请不要这样做。不要试图编写修改状态的函数——这不是函数式编程的工作方式。如果您需要交换两个值的函数,只需编写按相反顺序返回它们的函数即可

如果您仍然对交换2个值感兴趣,请参阅本文以获得一些非常好的建议。最重要的是宏和手动引用(实际值的包装)

然而,这些答案并没有包括一个重要的概念,这一概念只适用于普通的Lisp,而不适用于大多数其他Lisp方言--地点。但首先让我们回顾一下将变量传递给函数的两种方法。考虑C++中的以下示例:

void f(int x) {
    ...
}
int a = 5;
f(a);
这就是所谓的“传递值”策略:将
a
的值复制到参数
x
。由于
x
只是一个副本,如果在
f()
中修改它,原始变量
a
将不会发生任何变化

但是,在C++中,你也可以做以下的事情:

void f(int& x) {    
    ...
}
int a = 5; 
f(a);
此策略称为“按引用传递”-这里您将指针传递到
a
所在的内存中的位置。因此
x
a
指向同一块内存,如果修改
x
a
也会改变

函数式语言(包括CommonLisp)不允许通过引用将变量传递给函数。那么
setf
是如何工作的呢?事实证明,CL有一个定义内存中位置的概念(有时也称为“位置”)
setf
(扩展为
set
special form的宏)直接处理位置而不是值

总结如下:

  • 与大多数Lisp一样,Common Lisp只允许通过值将变量传递给函数
  • Lisp在内存中有位置的概念
  • setf
    直接与位置一起工作,可用于变异变量。宏可用于克服函数的限制 请注意,CL中的一些内置函数可以返回位置,例如
    car
    cdr
    aref
    以及所有对象访问器。有关示例,请参见第页

    更新

    ;;; doesn't change original
    (defun foo1 (x)
      (setf x (list 0 0 0)))
    
    ;;; but this does
    (defun foo4 (x)
      (setf (car x) 0)
      (setf (cdr x) (list 0 0)))
    
    您的新问题是在何处修改值-在函数内部通过引用修改值,或在没有引用的外部修改值。然而,这些在函数式编程中都是不正确的。正确的答案是:不要修改任何内容。在FP中,通常有一些状态变量,但不是就地修改它,而是创建修改后的副本并进一步传递它,这样原始变量就不会更改。考虑计算阶乘的递归函数的例子:

    (defun factorial-ex (x accum)
       (if (<= x 1) 
          accum
          (factorial-ex (- x 1) (* x accum))))
    
    (defun factorial (x)
       (factorial-ex x 1))
    
    (defun阶乘ex(x累计)
    
    (如果(首先,您必须确保正确理解任务。返回修改的输入并不等于修改输入

    返回修改的输入是微不足道的。考虑这个简单的例子:

    (defun foo (bar)
      (1+ bar))
    
    此函数将返回输入
    ,通过向其添加1进行修改。您可以考虑一个更通用的函数,该函数接受一个输入和一个修改例程,并将其应用于输入。此函数称为
    应用

    CL-USER> (apply '1+ '(1))
    2
    
    现在,如果您想修改传递给函数的变量的值,确实不可能直接进行修改,因为Lisp对函数应用程序使用按值传递,而不是按引用传递或按名称传递。因此,这类任务通常通过使用ca的特殊或通用修改宏来完成,如
    setf
    我叫你的名字

    然而,在有限的情况下,这里还有另一项工作可能很有用——您不能修改
    (defun swap (v1 v2)
      (psetf (elt v1 0) (elt v2 0)
             (elt v2 0) (elt v1 0)))
    CL-USER> (defvar *v1* #(0))
    CL-USER> (defvar *v2* #(1))
    CL-USER> (swap *v1* *v2*)
    CL-USER> (format t "~A ~A" *v1* *v2*)
    #(1) #(0)
    
    (defun make-hanoi-stack (&rest items)
      (cons items "unused slot"))
    (defun hanoi-stack-push (item hanoi-stack)
      (push item (car hanoi-stack)))
    (defun hanoi-stack-pop (hanoi-stack)
      (pop (car hanoi-stack)))
    (defun hanoi-stack-contents (hanoi-stack)
      (car hanoi-stack))
    
    (defun move-one-item (from-hanoi-stack to-hanoi-stack)
      (hanoi-stack-push (hanoi-stack-pop from-hanoi-stack)
                        to-hanoi-stack))
    
    (let ((stack1 (make-hanoi-stack 1 2 3))
          (stack2 (make-hanoi-stack 4)))
      (move-one-item stack1 stack2)
      (print (hanoi-stack-contents stack1))
      (print (hanoi-stack-contents stack2)))
    
    (defun f (xs) (setf xs (list 4 5 6))) ; this won't work
    (defun f (xs) (setf (car xs) 4) (setf (cdr xs) (list 5 6))) ; but this will
    (defun f (xs) (setf (elt xs 0) 4)) ; this will change xs to (4 2 3)