Common Lisp - Как использовать / использовать функцию с аргументами ключевых слов? - PullRequest
3 голосов
/ 02 апреля 2020

Context

  1. С такими функциями, как (lambda (List arg1 arg2 ... argn)), я могу использовать funcall / apply для вызова этих методов с исходными аргументами и, таким образом, изменить список внутри лямбды.
  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.

1 Ответ

2 голосов
/ 03 апреля 2020

Кажется, что есть недоразумение о том, что происходит, когда вы звоните:

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

Список аргументов args и (list :mem program-memory) используются для создания нового списка. Здесь вы могли бы использовать append, вот так: (append args (list :mem program-memory). В обоих случаях исходные списки не изменены, но вы получаете список аргументов fre sh (который может разделять последний список, но это деталь).

Однако содержимое обоих списков входных данных и результирующий список идентичен , точно такие же объекты упоминаются в этих списках до и после объединения, неявная копия объектов отсутствует.

Давайте посмотрим:

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

Когда я оцениваю *something*, результирующий объект печатается как #<SOMETHING {10091B1973}> (напечатанное представление может различаться в разных реализациях; идентичность вашего объекта будет отличаться).

Если я поместу его в list и вызовите 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)

NB. Неопределенное поведение заключается в изменении самого списка аргументов.


Редактировать:

Возможно, вы связали саму память с аргументами, и в этом случае новый список, представляющий память, был используется в вызываемой функции, но если это так, то это ошибка, поскольку предполагается, что конкатенация изменяет только список аргументов, а не один из аргументов.

...