Lisp参数指针
我正在学习lisp,我必须从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中可能吗
(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递归调用自己。问题是,当递归函数中的Ipop
或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的宏)直接处理位置而不是值
总结如下:
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)