(lambda (List arg1 arg2 ... argn))
I can use
funcall
/apply
to call those methods with the original arguments and thus modify the list
inside the lambda.(lambda (arg1 arg2 ... argn &key List))
I can only use funcall
/apply
with copies of the
arguments, which means
I cannot modify them inside the functions. With (lambda (mem arg1 arg2 ... argn))
:
;; Can pass the original lists for modification inside the function:
(funcall #'fn program-memory args-list)
functions can modify those lists.
With (lambda (arg1 arg2 ... argn &key mem))
, I can only call it with a copy of the original lists:
;; can only pass copies of the lists :(
(apply #'fn (concatenate 'list args (list :mem program-memory)))
Thus I can no longer modify the program memory.
How can I make it work? I.e., call the function with the original list and not a copy.
Example with simplified old code (like in 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
Full code is found at https://github.com/AlbertoEAF/advent_of_code_2019/blob/master/common-lisp/day5.lisp.
There seem to be a misunderstanding about what happens when you call:
(concatenate 'list args (list :mem program-memory))
The argument list args
and (list :mem program-memory)
are used to build a new list. Here you could have used append
, like so: (append args (list :mem program-memory)
. In both cases the original lists are unmodified, but you get a fresh list of arguments (that may share the last list, but this is a detail).
However, the content of both the inputs lists and the resulting list are identical, the exact same objects are referenced in those lists before and after concatenation, there is no implicit copy of objects.
Let's see:
(defclass something () ())
(defvar *something* (make-instance 'something))
When I evaluate *something*
, the resulting object is printed as #<SOMETHING {10091B1973}>
(the printed representation may vary across implemetations; the identity of your object would be different).
If I put it in a list, and call copy-list
, the resulting list still holds the same value:
(let ((list (list *something*)))
(assert (eq (first list)
(first (copy-list list)))))
The same applies if you store lists inside the list, they are not going to be copied recursively without an explicit call to copy. In fact let's try the same example you gave, but with keywords:
;; 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)
The resulting memory state is:
#(3 2 3 0 0 0 0 0)
NB. What is an undefined behaviour is mutating the argument list itself.
Edit:
Maybe you did concatenate the memory itself with the args, in which case a new list representing the memory was used in the called function, but if so, this is an error because the concatenation is only supposed to modify the list of arguments, not one of the argument.