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

1.2k Views Asked by At

Context

  1. With functions like (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.
  2. With functions like (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.
  3. How can I use functions like in 2. with the same functionality of those in 1.?

Issue in detail

1. Functions that work

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.

2. Functions that lose ability to modify arguments

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.

3. How can I make functions in 2. work like in 1.?

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.

1

There are 1 best solutions below

1
On BEST ANSWER

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.