The following function aims to make a symbol out of several arguments. However, calling it generates a keyword error.
(defun create-symbol (&rest objects &key intern (package *package*))
"Creates a symbol from the objects."
(let ((arg-string (format nil "~{~A~^~}" (first objects))))
(if intern
(values (intern arg-string package))
(make-symbol arg-string))))
For example, (create-symbol "A" 1)
produces Unknown &KEY argument: "A"
instead of #:A1
.
Also not sure if (first objects)
is the correct way to access the &rest arguments, if no keywords are submitted.
Thanks for any help verifying the intended operation of this function.
Edit: Given the comments below, it looks like manual parsing of the arguments may be one way to go when they are combinations of the lambda list keywords &optional, &rest, and &key. The following function seems to do what I was originally intending:
(defun create-symbol (&rest objects&keys)
"Creates a symbol from the objects,
with optional keywords :intern and :package."
(let* ((keys (member-if #'keywordp objects&keys))
(objects (ldiff objects&keys keys))
(arg-string (format nil "~{~A~^~}" objects)))
(if (getf keys :intern)
(intern arg-string (or (getf keys :package) *package*))
(make-symbol arg-string))))
A very small example showing what's going wrong is:
(defun test-key-rest (&rest args &key a (b t))
(list 'args args 'a a 'b b))
(test-key-rest :a 1 :b 2); => (ARGS (:A 1 :B 2) A 1 B 2)
(test-key-rest :a 1 :b 2 "rest now?");;; Error: The passed key "rest now?" is not defined for this function
(test-key-rest :a 1 :b 2 :c 3);;; Error: The passed key :C is not defined for this function
One could perhaps use &allow-other-keys, but I think it would be messy. I remembered reading about this kind of situation in Practical Common Lisp, where Peter Seibel writes (emphasis mine):
The other two combinations, either &optional or &rest parameters combined with &key parameters, can lead to somewhat surprising behavior.
I suggest separating the two argument lists. destructuring-bind makes it easy:
(defun test-two-arg-lists (keys &rest args)
(destructuring-bind (&key (a nil) (b t)) keys
(list 'a a 'b b 'args args)))
(test-two-arg-lists (list :a 1) "more" "please"); => (A 1 B T ARGS ("more" "please"))
But I (and I assume others) don't want to have to construct that first keyword argument list, so let's make it evaluate its arguments as we'd expect with a macro:
(defmacro test-two-nice (keys &rest args)
`(test-two-arg-lists (list ,@keys) ,@args))
(test-two-nice (:a 1) "more" "please"); => (A 1 B T ARGS ("more" "please"))
So to pull it all together:
(defun create-symbol-fn (keys &rest objects)
"Creates a symbol from the objects."
(destructuring-bind (&key intern (package *package*)) keys
(let ((arg-string (format nil "~{~A~}" objects)))
(if intern
(values (intern arg-string package))
(make-symbol arg-string)))))
(defmacro create-symbol (keys &rest objects)
`(create-symbol-fn (list ,@keys) ,@objects))
(create-symbol (:intern nil) 'a 'b 'c 'd); => #:ABCD