lisp中的变量引用

时间:2009-08-08 21:54:33

标签: lisp common-lisp

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

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

(defun increase-by-one (var)
  (setf var (+ var 1)))

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

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

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

7 个答案:

答案 0 :(得分:22)

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

思考功能!

(let ((a 1))
  (values (lambda (new-value)
            (setf a new-value)) 
          (lambda () a)))
上面的

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

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

(defun increase-by-one (writer reader)
   (funcall writer (1+ (funcall reader))))

所以,要做你想做的事,代码需要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*)

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

还有一个宏INCF可以增加“地点”(例如变量)。

(incf a)

a是当前范围内的变量。

(defun foo (a)
  (incf a))  ; increases the local variable a

此处可以看到限制

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

(let ((a 1))
   (foo something-referencing-a))

无法将a的直接引用传递给FOO

唯一的方法是提供一个功能。我们还必须重写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

答案 1 :(得分:9)

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

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

(defmacro increase-by-one (var)
  `(setf ,var (+ ,var 1)))

这允许您编写如下代码:

(increase-by-one foo)

将扩展为:

(setf foo (+ foo 1))

在编译之前。

答案 2 :(得分:7)

当然,如果你愿意,在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))

然后你可以使用它:

(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

答案 3 :(得分:1)

宏可能是您想要的,因为它们不会评估它们的参数,因此如果传递变量名称,则会得到变量名称,而不是其值。

INCF完全符合你的要求,所以如果你谷歌“defmacro incf”,你会找到一大堆定义,其中一些甚至接近正确。 : - )

编辑:我建议INCF不是编写自己的替代品,而是因为它可以做你想要的,并且是一个宏,所以你可以很容易地找到它的源代码,例如ABCL或{{ 3}}

答案 4 :(得分:0)

我认为你错过了函数式编程的一个关键概念 - 一旦创建了对象,你就不应该改变对象的状态。通过引用更改内容违反了这一点。

答案 5 :(得分:0)

您可以通过使用返回值来产生类似的效果,这是Lisp中的一种可能的设计模式,即使它没有通过引用传递变量。

赞:

(defun increase (subject increment)
  (+ subject increment))

(let ((my-var 0))
  (message "Initial value: %s" my-var)
  (setq my-var (increase my-var 25))
  (message "Final value: %s" my-var))

答案 6 :(得分:-3)

作为初学者,我来到这里是为了弄清楚如何在任何语言中做一个简单的程序。上面发布的大多数解决方案都不能正常工作,可能比需要的更复杂,或者不同的实现。以下是 SBCL 的简单解决方案:

(defmacro inc-by-num (var num)
           (set var (+ (eval var) num)))

显然你不能使用setf b / c限制范围,而set则不然。如果您收到“参数X不是数字”错误,您可能需要在eval之前使用var

相关问题