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中甚至可能吗? 这个功能怎么解决?

更新
;;; 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))

为了解释我为什么要做到这一点,我得到了汉诺塔的作业。我正在考虑使用三个列表作为 stacks并使用 poppush它们上的功能以插入和移除“光盘”。我定义了(move n source target temp)函数,它使用 n-1 递归调用自身改变。问题是,当我 poppush递归函数中的堆栈,它不会影响外部堆栈。
如果我想要我的 moven 之后返回堆栈的函数 Action ,我真的应该返回新堆栈列表(那个不太优雅的函数)而不是通过引用编辑它们(那个更优雅的函数)

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

最佳答案

首先,如果您正在学习函数式编程或一般的 Lisp,而不仅仅是 Common Lisp,不要这样做 .不要尝试编写修改状态的函数——这不是函数式编程的工作方式。如果您需要交换 2 个值的函数,只需编写以相反顺序返回它们的函数。

如果您仍然对交换 2 个值感兴趣,请参阅 similar question几个非常好的建议。最重要的是宏和手动引用(实际值的包装器)。

然而,这些答案不包含一个重要概念,仅在 Common 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 所在的位置。居住。因此xa指向同一 block 内存,如果修改x , a也会改变。

包括 Common Lisp 在内的函数式语言不允许您通过引用将变量传递给函数。那么如何setf作品?原来CL有place的概念(有时也称为“位置”)定义内存中的位置。 setf (扩展为 set 特殊形式的宏)直接使用位置而不是值。

总结一下:
  • Common Lisp 和大多数 Lisp 一样,只允许 通过函数变量仅按值 .
  • Lisp 有 场所概念 - 内存中的位置。
  • setf直接与地点一起工作,可用于变异变量。宏可用于克服功能的限制。

  • 请注意,CL 中的某些内置函数可以返回位置,例如car , cdr , aref以及所有对象访问器。见 this页面中的一些示例。

    更新

    您的新问题是在哪里修改值 - 在函数内部通过引用或在外部没有引用。但是,这些在函数式编程中都不正确。正确答案是:不要修改任何东西 .在 FP 中,您通常有一些状态变量,但不是在原地修改它,而是创建修改后的副本并进一步传递它,这样原始变量就不会改变。考虑用于计算阶乘的递归函数示例:
    (defun factorial-ex (x accum)
       (if (<= x 1) 
          accum
          (factorial-ex (- x 1) (* x accum))))
    
    (defun factorial (x)
       (factorial-ex x 1))
    
    factorial-ex是一个辅助函数,它需要一个额外的参数 - 累加器来保存当前的计算状态。在每个递归调用中,我们减少 x乘以 1 并乘以 accumx 的当前值计算.但是,我们不会更改 x 的值。和 accum - 我们将新值传递给函数的递归调用。物理上有许多 x 的副本和 accum - 每个函数调用一个 - 它们都没有改变。

    (请注意,某些具有特定选项的 CL 实现可能会使用所谓的 tail call optimization 来破坏关于上述内存中不同位置的声明,但目前您不必担心。)

    在你的任务中,你可以做同样的事情。而不是修改你的 3 个变量 - 无论是内部还是外部函数 - 制作修改后的副本并将它们传递给递归调用。在命令式编程中,您使用变量和循环,而在函数式编程中,您应该更喜欢不可变值和递归。

    关于Lisp 参数指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15233522/

    相关文章:

    c - 识别 : eyes open/closed, 嘴巴张开/闭合、头部运动的库

    lisp - 我如何使用 cl-async 在 Lisp 中检索套接字对等方的地址

    lisp - 专门针对两个参数的泛型函数(多重方法)

    lisp - 如何提取最大值中的子表达式?

    lisp - deftype - 变量未绑定(bind)

    lisp - 比较 Lisp 中的列表

    macros - 在 Lisp 中,作为 push 是对 cons 的追加是什么?

    lisp - 在 lisp 中,我如何测量和捕获评估表达式所花费的时间?

    lisp - Lisp 是像 JVM 一样的虚拟机吗?

    list - lisp 中列表元素的两个元素组合(没有重复项)