lisp中的变量引用

Tam*_*ege 26 lisp common-lisp

另一个新手(Common)LISP问题:

基本上在大多数编程语言中,函数接受变量的引用而不仅仅是值,即通过引用传递而不是传递值.让我们说,为了简单起见,我想编写一个LISP函数来接收变量并将变量的值增加一:

(defun increase-by-one (var)
  (setf var (+ var 1)))
Run Code Online (Sandbox Code Playgroud)

现在显然问题是这个函数只增加了堆栈上变量副本的值,而不是实际的原始变量.我也试图通过使用宏来取得成效,虽然我有这种感觉,使用宏是正确的方法.

我在LISP中一直打到这堵墙,我确信必须有办法解决这个问题,或者在LISP中我有没有想过这个问题的方法完全不同?这样的事情是如何在LISP中完成的?

编辑:多人建议使用incf.我只用这个例子以简单的方式演示问题,我实际上并没有寻找重新实现incf.但无论如何,谢谢你的建议.

Rai*_*wig 25

对于词法范围,人们无法访问不在当前范围内的变量.您也不能直接将词法变量传递给其他函数.Lisp评估变量并传递绑定到这些变量的值.没有什么比变量的第一类引用更好了.

想想功能!

(let ((a 1))
  (values (lambda (new-value)
            (setf a new-value)) 
          (lambda () a)))
Run Code Online (Sandbox Code Playgroud)

上面返回两个函数.可以读取变量,另一个可以写变量.

我们先调用第一个函数,writer然后调用第二个函数reader.

(defun increase-by-one (writer reader)
   (funcall writer (1+ (funcall reader))))
Run Code Online (Sandbox Code Playgroud)

因此,为了做你想做的事,代码需要a)在范围内或b)访问范围内的函数.

变量也可以是全局变量.

(defvar *counter* 1)

(defun increase-by-one (symbol)
  (set symbol (1+ (symbol-value symbol))))
  ; note the use of SET to set a symbol value

(increase-by-one '*counter*)
Run Code Online (Sandbox Code Playgroud)

这适用于由符号表示的全局变量.它对词法变量不起作用 - 这些变量不用符号表示.

还有一个INCF增加"位置" 的宏(例如变量).

(incf a)
Run Code Online (Sandbox Code Playgroud)

但是a当前范围内的变量.

(defun foo (a)
  (incf a))  ; increases the local variable a
Run Code Online (Sandbox Code Playgroud)

这里有限制:

(defun foo (var)
  (add-one-some-how var))

(let ((a 1))
   (foo something-referencing-a))
Run Code Online (Sandbox Code Playgroud)

有没有办法通过直接引用aFOO.

唯一的方法是提供一个功能.我们还必须重写FOO,以便它调用提供的函数.

(defun foo (f)
  (funcall f 1))   ; calls the function with 1

(let ((a 1))
   (foo (lambda (n)
          (setf a (+ a n)))))
   ;; passes a function to foo that can set a
Run Code Online (Sandbox Code Playgroud)


Gre*_*ill 9

虽然Common Lisp 支持函数式编程风格,但这并不是它的一般重点(Scheme虽然不是纯粹的功能,但更接近).Common Lisp支持完全命令式的编程风格.

如果您发现需要编写类似的代码,通常的解决方案是宏:

(defmacro increase-by-one (var)
  `(setf ,var (+ ,var 1)))
Run Code Online (Sandbox Code Playgroud)

这允许您编写如下代码:

(increase-by-one foo)
Run Code Online (Sandbox Code Playgroud)

这将扩展为:

(setf foo (+ foo 1))
Run Code Online (Sandbox Code Playgroud)

在编译之前.

  • 更好的解决方案是使用INCF,特别是因为你的INSCREASE-BY-ONE有缺陷并且对VAR进行了两次评估. (8认同)
  • @DrJokepu:`setf` 支持`(setf (car foo) bar)` 甚至`(setf (fifth list) 42)` 之类的操作。当与上述宏示例一起使用时,`(第五个列表)` 将被评估两次:有关更多信息,请参见此处:http://www.supelec.fr/docs/cltl/clm/node80.html (2认同)

dmi*_*_vk 8

当然,如果你愿意,在Lisp中你可以自己制作变量引用.最简单的方法是这样的:

(defstruct reference getter setter)

(defmacro ref (place)
  (let ((new-value (gensym)))
    `(make-reference :getter (lambda () ,place)
                     :setter (lambda (,new-value)
                               (setf ,place ,new-value)))))

(defun dereference (reference)
  (funcall (reference-getter reference)))

(defun (setf dereference) (new-value reference)
  (funcall (reference-setter reference) new-value))
Run Code Online (Sandbox Code Playgroud)

然后你可以使用它:

(defun increase-by-one (var-ref)
  (incf (dereference var-ref)))

(defun test-inc-by-one (n)
  (let ((m n))
    (increase-by-one (ref m))
    (values m n)))

(test-inc-by-one 10) => 11, 10
Run Code Online (Sandbox Code Playgroud)