Lisp 两个简单的推送功能;一个永久性地突变全局var,另一个不';t、 为什么??

Lisp 两个简单的推送功能;一个永久性地突变全局var,另一个不';t、 为什么??,lisp,common-lisp,sbcl,Lisp,Common Lisp,Sbcl,下面是两个简单的函数,它们对传入的变量使用push: (取消推送静止(var)(推送99(静止var))) 及 (只需按一下(某物)(按5个某物)) 第一个将永久性地改变传递的var。第二种情况并非如此。对于正在学习这种语言的范围界定行为的人来说,这是相当令人困惑的: CL-USER> (defparameter something (list 1 2)) SOMETHING CL-USER> something (1 2) CL-USER> (just-push so

下面是两个简单的函数,它们对传入的变量使用
push

(取消推送静止(var)(推送99(静止var)))

(只需按一下(某物)(按5个某物))

第一个将永久性地改变传递的
var
。第二种情况并非如此。对于正在学习这种语言的范围界定行为的人来说,这是相当令人困惑的:

CL-USER> (defparameter something (list 1 2))    
SOMETHING
CL-USER> something
(1 2)
CL-USER> (just-push something)
(5 1 2)
CL-USER> something
(1 2)
CL-USER> (push-rest something)
(99 2)
CL-USER> something
(1 99 2)
push-rest
中,为什么不像
中那样,var的作用域不是函数的局部作用域,只是push
,当它们都使用相同的函数时,
push

您不能推送传递的变量。Lisp不传递变量。

Lisp传递对象

你需要了解评估

(just-push something)
Lisp认为,
只需按一下
就是一个函数

现在它计算
某个东西
。某物的值是一个列表
(12)

然后它调用
,只需使用一个参数
(12)
按一下

只要按一下
就不会看到变量,它不在乎。它得到的只是对象

(defun push-rest (some-list) (push 99 (rest some-list)))
上面将
99
推到传递列表的剩余部分,即cons。因为cons在外部可见,所以更改在外部可见

(defun just-push (something) (push 5 something))
(push item place)
上面将
5
推送到
something
指向的列表。由于
某些内容在外部不可见,并且没有进行其他更改,因此该更改在外部不可见。

您不能推送传递的变量。Lisp不传递变量。

(defun just-push (something) (push 5 something))
(push item place)
Lisp传递对象

你需要了解评估

(just-push something)
Lisp认为,
只需按一下
就是一个函数

现在它计算
某个东西
。某物的值是一个列表
(12)

然后它调用
,只需使用一个参数
(12)
按一下

只要按一下
就不会看到变量,它不在乎。它得到的只是对象

(defun push-rest (some-list) (push 99 (rest some-list)))
上面将
99
推到传递列表的剩余部分,即cons。因为cons在外部可见,所以更改在外部可见

(defun just-push (something) (push 5 something))
(push item place)
上面将
5
推送到
something
指向的列表。由于
某些东西在外部不可见,并且没有进行其他更改,因此该更改在外部不可见

(defun just-push (something) (push 5 something))
(push item place)
它的工作原理如下 当表单用于指示
放置在
集合中所指的位置时

(setf place (cons item place))
它的工作原理如下 当表单用于指示
放置在
集合中所指的位置时

(setf place (cons item place))

push
将符号或列表作为第二个参数传递时,其工作方式不同。如果您在两个不同的页面上执行
macroexpand
,您可能会更好地理解它

(macroexpand '(push 99 (rest var))) 
;;==>
(let* ((#:g5374 99)) 
  (let* ((#:temp-5373 var)) 
    (let* ((#:g5375 (rest #:temp-5373))) 
      (system::%rplacd #:temp-5373 (cons #:g5374 #:g5375)))))
现在,大多数情况下,我们不会对参数进行多次求值,因此在本例中,我们可以将其重写为:

(rplacd var (cons 99 (rest var)))
现在,这改变了var的
cdr
,使得每个绑定到同一个值或在其结构中具有相同对象的列表的绑定都会改变。现在让我们试试另一个:

(macroexpand '(push 5 something))
; ==>
(setq something (cons 5 something))
这里是创建一个从5开始的新列表,并修改绑定到该值的局部函数,该值在开始时指向原始结构。如果在变量
lst
中有原始结构,则不会对其进行更改,因为它是一个与
something
完全不同的绑定。您可以使用宏修复问题:

(defmacro just-push (lst)
  (if (symbolp lst)
      `(push 5 ,lst)
      (error "macro-argument-not-symbol")))
这只接受变量作为参数,并将其变为一个新列表,第一个元素为5,最后一个元素为原始列表
(按x键)
只是
(按5X键)
的缩写

我只是想说清楚。在Algol方言中,等效代码类似于:

public class Node
{
  private int value;
  private Node next;

  public Node(int value, Node next)
  {
    this.value = value;
    this.next  = next;
  }

  public static void pushRest(Node var)
  {
    Node n   = new Node(99, var.next); // a new node with 99 and chained with the rest of var
    var.next = n; // argument gets mutated to have new node as next
  }

  public static void justPush(Node var)
  {
    var = new Node(5, var); // overwrite var
    System.out.print("var in justPush is: ");
    var.print();
  }

  public void print()
  {
    System.out.print(String.valueOf(value) + " ");
    if ( next == null )
      System.out.println();
    else
      next.print();
  }

  public static void main (String[] args)
  {
    Node n = new Node( 10, new Node(20, null)); 
    n.print();    // displays "10 20"
    pushRest(n);  // mutates list
    n.print();    // displays "10 99 20"
    justPush(n);  // displays "var in justPush is :5 10 99 20" 
    n.print();    // displays "10 99 20"
  }
}

push
将符号或列表作为第二个参数传递时,其工作方式不同。如果您在两个不同的页面上执行
macroexpand
,您可能会更好地理解它

(macroexpand '(push 99 (rest var))) 
;;==>
(let* ((#:g5374 99)) 
  (let* ((#:temp-5373 var)) 
    (let* ((#:g5375 (rest #:temp-5373))) 
      (system::%rplacd #:temp-5373 (cons #:g5374 #:g5375)))))
现在,大多数情况下,我们不会对参数进行多次求值,因此在本例中,我们可以将其重写为:

(rplacd var (cons 99 (rest var)))
现在,这改变了var的
cdr
,使得每个绑定到同一个值或在其结构中具有相同对象的列表的绑定都会改变。现在让我们试试另一个:

(macroexpand '(push 5 something))
; ==>
(setq something (cons 5 something))
这里是创建一个从5开始的新列表,并修改绑定到该值的局部函数,该值在开始时指向原始结构。如果在变量
lst
中有原始结构,则不会对其进行更改,因为它是一个与
something
完全不同的绑定。您可以使用宏修复问题:

(defmacro just-push (lst)
  (if (symbolp lst)
      `(push 5 ,lst)
      (error "macro-argument-not-symbol")))
这只接受变量作为参数,并将其变为一个新列表,第一个元素为5,最后一个元素为原始列表
(按x键)
只是
(按5X键)
的缩写

我只是想说清楚。在Algol方言中,等效代码类似于:

public class Node
{
  private int value;
  private Node next;

  public Node(int value, Node next)
  {
    this.value = value;
    this.next  = next;
  }

  public static void pushRest(Node var)
  {
    Node n   = new Node(99, var.next); // a new node with 99 and chained with the rest of var
    var.next = n; // argument gets mutated to have new node as next
  }

  public static void justPush(Node var)
  {
    var = new Node(5, var); // overwrite var
    System.out.print("var in justPush is: ");
    var.print();
  }

  public void print()
  {
    System.out.print(String.valueOf(value) + " ");
    if ( next == null )
      System.out.println();
    else
      next.print();
  }

  public static void main (String[] args)
  {
    Node n = new Node( 10, new Node(20, null)); 
    n.print();    // displays "10 20"
    pushRest(n);  // mutates list
    n.print();    // displays "10 99 20"
    justPush(n);  // displays "var in justPush is :5 10 99 20" 
    n.print();    // displays "10 99 20"
  }
}

根据Peter Siebel的实用通用Lisp,第6章。变量:可能对您有很大帮助:

与所有常见的Lisp变量一样,函数参数保存对象引用。因此,您可以为函数体中的函数参数指定一个新值,而这不会影响为同一函数的另一个调用创建的绑定。但是,如果传递给函数的对象是可变的,并且您在函数中对其进行了更改,则调用方将看到这些更改,因为调用方和被调用方都将引用同一对象

和脚注:

在编译器编写器术语中,常见的Lisp函数是“按值传递”。然而,传递的值是对对象的引用

(passby value本质上也意味着copy;但我们不是在复制对象;我们是在复制指向对象的引用/指针。)

正如我在另一篇评论中指出的:

Lisp不传递对象。Lisp传递对象r的副本