I am using Emacs, Slime, and SBCL. Simplifying a problem that I am facing, suppose I have this function working:
(defun get-answer (x y z)
(format t "Which animal would you like to be: ~s ~s ~s ~%" x y z)
(let ((answer (read-line)))
(cond ((equal answer x) (format t "user picks ~s" x))
((equal answer y) (format t "user picks ~s" y))
((equal answer z) (format t "user picks ~s" z)))))
It works for a fixed number of inputs:
CL-USER> (get-answer "fish" "cat" "owl")
Which animal would you like to be: "fish" "cat" "owl"
fish
user picks "fish"
NIL
I would like to generalize this function writing a variant argument macro that builds a variant number of cond
clauses. Basically, Common Lisp macro would write the cond clause for me :)
For instance, I wish I could call it like:
CL-USER> (get-answer '("pig" "zebra" "lion" "dog" "cat" "shark"))
Or just:
CL-USER> (get-answer '("dog" "cat"))
Either way, it would generate 6 and 2 appropriate cond
clauses, respectively. I tried building something to achieve this goal.
My draft/sketch focusing on the cond
clause part is:
(defmacro macro-get-answer (args)
`(cond
(,@(mapcar (lambda (str+body)
(let ((str (first str+body))
(body (second str+body)))
`((string= answer ,str)
,body)))
args))))
The variable answer
was supposed to hold the value inserted by the user. However, I can't manage to make it work. slime-macroexpand-1
is not being really helpful.
As an error message, I get:
The value
QUOTE
is not of type
LIST
Which is not something that I was expecting. Is there a way to fix this?
Thanks.
I don't think you need a macro. Note that there's a repeating pattern of ((equal answer x) (format t "user picks ~s" x))
, so you should think how to simplify that.
So, this is your function and expected inputs:
(defun get-answer (x y z)
(format t "Which animal would you like to be: ~s ~s ~s ~%" x y z)
(let ((answer (read-line)))
(cond ((equal answer x) (format t "user picks ~s" x))
((equal answer y) (format t "user picks ~s" y))
((equal answer z) (format t "user picks ~s" z)))))
(get-answer '("pig" "zebra" "lion" "dog" "cat" "shark"))
(get-answer '("dog" "cat"))
I'd write something like this:
(defun get-answer (args)
(format t "Which animal would you like to be: ~{~s ~} ~%" args)
(let ((answer (read-line)))
(when (member answer args :test #'string=)
(format t "User picks ~s" answer)
answer)))
Tests:
(get-answer '("pig" "zebra" "lion" "dog" "cat" "shark"))
(get-answer '("dog" "cat"))
Note that your function always returned NIL
, mine returns chosen string or NIL
. After you recieve user input, you probably don't want to discard it immediately.
And if you have string with values, like this:
(get-answer '(("pig" 1) ("zebra" 3) ("lion" 2) ("dog" 8) ("cat" 12) ("shark" 20)))
I'd use find
:
(defun get-answer (args)
(format t "Which animal would you like to be: ~{~s ~} ~%" (mapcar #'first args))
(let* ((answer (read-line))
(match (find answer args :test #'string= :key #'car)))
(when match
(format t "User picks ~s " (first match))
(second match))))
This function returns NIL
or value of chosen animal.