Reputation: 87
My goal is to have a macro that builds functions automatically with an argument list generated elsewhere. I want the macro to return a list composed of the function and the argument list it used (list of symbols). I'm using SBCL.
Let's say the argument list is generated by :
(defun input-syms ()
(list 'in1 'in2 'in3))
;;=> (IN1 IN2 IN3)
Following the useful answer in Nested `defun` produces a repeated warning in Allegro Common Lisp, I used labels
like so (adding the elements of a list, just for the sake of example) :
(defmacro create-funtest ()
(let ((input-list (input-syms)))
`(labels ((fun-created ,input-list
(reduce #'+ (list ,@input-list))))
#'fun-created)))
(funcall (create-funtest) 2 2 3) ;=> 7
This seems to work, although I think there could be a simpler way of doing this. (list ,@input-list)
seems unnecessary , but replacing it with just ,input-list
does not work.
This is where I am at a loss and it seems related to the issue of what exactly ,input-list
means. I think this has something to do with the fact that we're manipulating symbols so I tried throwing symbol-value
in there but to no avail.
What I would like to obtain, expressed in not-working code would be :
(defmacro create-funtest2 ()
(let ((input-list (input-syms)))
`(labels ((fun-created ,input-list
(reduce #'+ (list ,@input-list))))
(list #'fun-created ,input-list))))
Which must return : (#<FUNCTION ...> (IN1 IN2 IN3))
.
But calling create-funtest2
gives a compilation error The variable IN2 is unbound.
. I think it's trying to evaluate the symbol instead of giving me the symbol as is.
I need to be able to get the symbols that were used to build the function, I use them when calling the function afterwards to know which input is what. This list of symbols can also be modified by the create-funtest
macro, so I really need to get it from inside the macro.
Thanks Rainer Joswig for the answer. The thing that is bothering me is actually returning the symbol list as symbols. I guess the expanded code for create-funtest2
(from the expansion you've given) should look like :
(LABELS ((FUN-CREATED (IN1 IN2 IN3)
(REDUCE #'+ (LIST IN1 IN2 IN3))))
(LIST #'FUN-CREATED (LIST 'IN1 'IN2 'IN3)))
So that the output of the macro is (#<FUNCTION ...> (IN1 IN2 IN3))
.
The issue is that I want to evaluate input-list
, but keep its elements as symbols (not sure I'm wording that right sorry).
Thank you coredump. The working version of create-funtest2
is :
(defmacro create-funtest2 ()
(let ((input-list (test-input-syms)))
`(labels ((fun-created ,input-list
(reduce #'+ (list ,@input-list))))
(list #'fun-created (quote ,input-list)))))
Which gives the expansion (thank you Rainer for the code snippet):
(let ((*print-circle* t)
(*PRINT-RIGHT-MARGIN* 50))
(pprint (copy-tree (macroexpand-1 '(create-funtest2)))))
=>
(LABELS ((FUN-CREATED (IN1 IN2 IN3)
(REDUCE #'+ (LIST IN1 IN2 IN3))))
(LIST #'FUN-CREATED '(IN1 IN2 IN3)))
And is called in the following way :
(defparameter *fun-created2* (create-funtest2))
(funcall (car *fun-created2*) 1 2 3) ; => 6, OK
(second *fun-created2*) ; => (IN1 IN2 IN3), OK
Upvotes: 3
Views: 100
Reputation: 38809
The reason you might want to use reduce
when given an arbitrary long list of inputs is that function calls are limited by CALL-ARGUMENT-LIMIT
. Here, however, you are using your list of symbols as function parameters, which is limited by LAMBDA-PARAMETERS-LIMIT
. Also, the second must be greater or equal to the first. So if the list of symbols is short enough to serve as a list of parameters, it is also short enough to be used as arguments in the call of +
.
(defun input-symbols ()
'(in1 in2 in3))
(defmacro create-funtest ()
(let ((args (input-symbols)))
`(lambda ,args (+ ,@args))))
Here above, I also used an anonymous function, but this is not important.
Your second version, rewritten using the same approach as above, is:
(defmacro bad-create-funtest2 ()
(let ((args (input-symbols)))
`(list (lambda ,args (+ ,@args))
,args)))
What does macroexpand
say about this?
(macroexpand '(bad-create-funtest2))
=> (LIST (LAMBDA (IN1 IN2 IN3) (+ IN1 IN2 IN3))
(IN1 IN2 IN3))
Here, you can see that you are trying to call in1
with arguments in2
and in3
.
You do not want to evalutate the list of symbols, just pass it unevaluated.
(defmacro create-funtest2 ()
(let ((args (input-symbols)))
`(list (lambda ,args (+ ,@args))
(quote ,args))))
By quoting the value, you can ensure the value will not be evaluated.
(macroexpand '(create-funtest2))
=> (LIST (LAMBDA (IN1 IN2 IN3) (+ IN1 IN2 IN3))
'(IN1 IN2 IN3))
Upvotes: 2
Reputation: 139261
Inputlist is (IN1 IN2 IN3)
.
This works:
(reduce #'+ (list IN1 IN2 IN3))
This does not work:
(reduce #'+ (IN1 IN2 IN3))
Reason: There is no function IN1
.
Macroexpand is your friend:
CL-USER 58 > (let ((*print-circle* t)
(*PRINT-RIGHT-MARGIN* 50))
(pprint (copy-tree (macroexpand-1 '(create-funtest2)))))
(LABELS ((FUN-CREATED (IN1 IN2 IN3)
(REDUCE #'+ (LIST IN1 IN2 IN3))))
(LIST #'FUN-CREATED (IN1 IN2 IN3)))
The expanded code has two problems:
IN1
in the last line does not existIN2
and IN3
in the last line does not existMaybe the macroexpand
will help you to solve your problem.
For more help you'd need to come up with slightly better explanation what you want to do.
Upvotes: 2