3

语境

  1. 使用像我这样的函数,(lambda (List arg1 arg2 ... argn))我可以使用 funcall/apply使用原始参数调用这些方法,从而修改 lambda 中的列表。
  2. 对于像我这样的函数,(lambda (arg1 arg2 ... argn &key List))我只能将funcall/apply与参数的副本一起使用,这意味着我不能在函数内部修改它们。
  3. 如何使用 2. 中的功能与 1. 中的功能相同?

详细问题

1. 有效的功能

(lambda (mem arg1 arg2 ... argn))

;; Can pass the original lists for modification inside the function:
(funcall #'fn program-memory args-list) 

函数可以修改这些列表。

2. 无法修改参数的函数

使用(lambda (arg1 arg2 ... argn &key mem)),我只能使用原始列表的副本来调用它:

;; can only pass copies of the lists :(
(apply #'fn (concatenate 'list args (list :mem program-memory))) 

因此我不能再修改程序内存。

3. 如何使 2. 中的功能像 1. 中一样工作?

我怎样才能让它工作?即,使用原始列表而不是副本调用函数。


简化旧代码的示例(如 1.):

(defun mem/w (memory address value)
  "Writes the value to memory at address. Returns nil."
  (setf (elt memory address) value)
  nil)

;; sum
(defun sum-op (mem a b o)
       (mem/w mem o (+ a b)))

(let ((program (list 1 2 3 4 5 6 7 8))
      (args    (list 1 2 0)))
  (apply #'sum-op
         (cons program args))
  (print program)) ;; shows modification --> good

完整代码可在https://github.com/AlbertoEAF/advent_of_code_2019/blob/master/common-lisp/day5.lisp找到。

4

1 回答 1

2

关于调用时会发生什么似乎存在误解:

(concatenate 'list args (list :mem program-memory))

参数列表args(list :mem program-memory)用于构建新列表。在这里您可以使用append,如下所示:(append args (list :mem program-memory). 在这两种情况下,原始列表都未修改,但您会得到一个新的参数列表(可能共享最后一个列表,但这是一个细节)。

但是,输入列表和结果列表的内容是相同的,在连接前后的列表中引用了完全相同的对象,没有对象的隐式副本。

让我们来看看:

(defclass something () ())
(defvar *something* (make-instance 'something)) 

当我评估*something*时,结果对象被打印为#<SOMETHING {10091B1973}>(打印的表示可能因实现而异;您的对象的身份会有所不同)。

如果我把它放在一个列表中,然后调用copy-list,结果列表仍然保持相同的值:

(let ((list (list *something*)))
  (assert (eq (first list)
          (first (copy-list list)))))

如果您将列表存储在列表中,这同样适用,如果没有显式调用复制,它们不会被递归复制。实际上,让我们尝试您给出的相同示例,但使用关键字:

;; unrelated, but for this Advent of Code you are going to have
;; performance issues if you treat memory as a list, and not a vector.
;; Here I change it to a vector.
(defun mem/w (memory address value)
  "Writes the value to memory at address"
  (setf (svref memory address) value))

;; mem is now a keyword argument
(defun sum-op (a b o &key mem)
  (mem/w mem o (+ a b)))

(let ((memory (vector 0 2 3 0 0 0 0 0))
      (args (list 1 2 0)))
  ;; using the backquote/slice syntax for brevity
  ;; but the result is like concatenate/append
  (apply #'sum-op `(,@args :mem ,memory))
  memory)

生成的内存状态为:

#(3 2 3 0 0 0 0 0)

注意。未定义的行为是改变参数列表本身。


编辑:

也许您确实将内存本身与 args 连接起来,在这种情况下,在被调用的函数中使用了一个表示内存的新列表,但如果是这样,这是一个错误,因为连接只应该修改参数列表,而不是一个的论点。

于 2020-04-02T22:07:54.017 回答