Common Lisp - 如何 funcall/apply 带有关键字参数的函数?

Common Lisp - How to funcall/apply a function with keyword arguments?

上下文

  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

关于调用时发生的情况似乎存在误解:

(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 连接在一起,在这种情况下,在被调用函数中使用了一个表示内存的新列表,但如果是这样,这是一个错误,因为连接只应该修改列表参数,而不是参数之一。