Reputation: 1941
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))))
Upvotes: 3
Views: 966
Reputation: 363
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
Upvotes: 2
Reputation: 4360
Basically you can’t do it this way. Either make intern
not a keyword argument or do your own keyword parsing. Here are the rules for argument parsing for ordinary functions:
&optional
) are required arguments. They must be passed. For further steps, only the arguments passed after the required arguments are counted.&optional
and then optional arguments. If there are any passed arguments yet to be parsed, these are taken as optional arguments until no optional arguments are left to parse. If there are no arguments left to parse then we are done.&rest
followed by a symbol to bind), keyword arguments (prefixed by &key
with the &allow-other-keys
keyword modifying the parsing. Any passed arguments not yet parsed at this stage are called rest arguments. This is how they are parsed:
&rest
argument in the lambda list, bind it to whatever arguments haven’t been parsed.&allow-other-keys
keyword was specified. One can imagine the following transformation:
(defun f ( { args } [ &rest rest ] &key { kwargs } [ &allow-other-keys ] )
...)
;; - - ->
(defun f ( { args } &rest rest )
(destructuring-bind ( &key { kwargs } [ &allow-other-keys ] ) rest
...))
This might make it a bit more clear.
It is possible for you to make your function behave how you want (not using getf
though because of parity) but I would argue that it is wrong. Consider the following:
(defun foobar-sym (k)
(create-symbol 'foo k 'bar))
CL-USER> (foobar-sym :baz)
#:FOOBAZBAR
CL-USER> (foobar-sym :intern)
FOO
this is slightly weird.
Upvotes: 2