【问题标题】:Variable references in lisplisp 中的变量引用
【发布时间】:2010-11-18 00:16:02
【问题描述】:

另一个新手(常见)LISP 问题:

基本上,在大多数编程语言中,函数都有一个方法来接收对变量的引用而不仅仅是值,也就是说,通过引用传递而不是通过值传递。比方说,为了简单起见,我想写一个 LISP 函数,它接收一个变量并将该变量的值加一:

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

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

我在 LISP 中一直碰壁,我确信一定有办法绕过它,或者我没有想过在 LISP 中有完全不同的方法来解决这个问题?这样的事情在 LISP 中是如何完成的?

编辑:多人建议使用incf。我只是用这个例子简单地演示了这个问题,我实际上并不是在寻找重新实现 incf。不过还是谢谢你的建议。

【问题讨论】:

  • 您说的是“大多数编程语言”,但除了 C++ 和 Perl 之外,还有其他语言具有这种特性吗?
  • @LeCurious Pascal 是另一个

标签: lisp common-lisp


【解决方案1】:

对于词法范围,一个人无法访问不在当前范围内的变量。您也不能直接将词法变量传递给其他函数。 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

【讨论】:

  • 很好的解释,对我帮助很大。谢谢!
【解决方案2】:

当然,在 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】:

    虽然 Common Lisp支持一种函数式编程风格,但这不是它的普遍关注点(Scheme 虽然不是纯粹的函数式,但更接近)。 Common Lisp 非常支持完全命令式的编程风格。

    如果你发现需要编写这样的代码,通常的解决方案是宏:

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

    这允许您编写如下代码:

    (increase-by-one foo)
    

    将扩展为:

    (setf foo (+ foo 1))
    

    在编译之前。

    【讨论】:

    • 更好的解决方案是使用 INCF,特别是因为您的 INSCREASE-BY-ONE 有问题并且会评估 VAR 两次。
    • 这很好,我的例子如果使用setq 而不是setf 会更好。 setf 宏背后有一个完整的代码世界来支持广义位置变量,所以上面的increase-by-one 宏不适合与setf 一起使用。
    • @Luís Oliveira:是的,这也是我对宏观方法感到困惑的原因。尽管我看不出两次评估变量可能是个问题。我的意思是,它不是一个表达式,它只是一个变量。还是我错过了什么?
    • @DrJokepu: setf 支持(setf (car foo) bar) 甚至(setf (fifth list) 42) 之类的操作。当与上述宏示例一起使用时,(fifth list) 将被评估两次:有关更多信息,请参见此处:supelec.fr/docs/cltl/clm/node80.html
    【解决方案4】:

    宏可能是你想要的,因为它们不评估它们的参数,所以如果你传递一个变量名,你会得到一个变量名,而不是它的值。

    INCF 完全符合您的要求,因此如果您在 Google 上搜索“defmacro incf”,您会发现一大堆定义,其中一些甚至接近正确。 :-)

    编辑:我建议不要将 INCF 作为自己编写的替代方案,而是因为它可以满足您的需求,并且是一个宏,因此您可以轻松找到它的源代码,例如 ABCLCMUCL

    【讨论】:

    • 谢谢!顺便说一句,我知道 incf,这只是一个演示问题的例子,当然我并没有尝试重新实现 incf。
    【解决方案5】:

    我认为您错过了函数式编程的关键概念之一——一旦对象被创建,您就不应该更改它们的状态。通过引用更改某些内容违反了这一点。

    【讨论】:

    • 破坏在编程中很常见
    • 您不应该假设一个必须在 Common Lisp 中以函数式风格编写程序。它的优点之一是它是多范式的,不会强加给你一种编程风格。如果您想查看一个示例,说明该事实被明确用于构建语言的介绍性文本,请查看 D. Touretsky 的“Common Lisp: A Gentle Introduction to Symbolic Computation”。
    • 它在 Common Lisp 中得到了很好的支持,它不是一种纯函数式语言。
    【解决方案6】:

    您可以通过使用返回值来产生类似的效果,这在 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))
    

    【讨论】:

      【解决方案7】:

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

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

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

      【讨论】:

      • Greg 的解决方案要好得多,您不会遇到一直调用 eval 的奇怪风险...
      • 没有。这不像你想象的那样工作。在这里我没有时间和地点展示错综复杂的机制,恰好让你相信你想要的效果已经达到,所以我只是提示一下:这里的设置发生在宏扩展时。跨度>
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多