可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I've been attempting to write a Lisp macro that would perfom the equivalent of ++ in other programming languages for semantic reasons. I've attempted to do this in several different ways, but none of them seem to work, and all are accepted by the interpreter, so I don't know if I have the correct syntax or not. My idea of how this would be defined would be
(defmacro ++ (variable)
(incf variable))
but this gives me a SIMPLE-TYPE-ERROR when trying to use it. What would make it work?
回答1:
Remember that a macro returns an expression to be evaluated. In order to do this, you have to backquote:
(defmacro ++ (variable)
`(incf ,variable))
回答2:
Both of the previous answers work, but they give you a macro that you call as
(++ varname)
instead of varname++ or ++varname, which I suspect you want. I don't know if you can actually get the former, but for the latter, you can do a read macro. Since it's two characters, a dispatch macro is probably best. Untested, since I don't have a handy running lisp, but something like:
(defun plusplus-reader (stream subchar arg)
(declare (ignore subchar arg))
(list 'incf (read stream t nil t)))
(set-dispatch-macro-character #\+ #\+ #'plusplus-reader)
should make ++var actually read as (incf var).
回答3:
I would strongly advise against making an alias for incf. It would reduce readability for anyone else reading your code who have to ask themselves "what is this? how is it different from incf?"
If you want a simple post-increment, try this:
(defmacro post-inc (number &optional (delta 1))
"Returns the current value of number, and afterwards increases it by delta (default 1)."
(let ((value (gensym)))
`(let ((,value ,number))
(incf ,number ,delta)
,value)))
回答4:
The syntax (++ a)
is a useless alias for (incf a)
. But suppose you want the semantics of post-increment: retrieve the old value. In Common Lisp, this is done with prog1
, as in: (prog1 i (incf i))
. Common Lisp doesn't suffer from unreliable or ambiguous evaluation orders. The preceding expression means that i
is evaluated, and the value is stashed somewhere, then (incf i)
is evaluated, and then the stashed value is returned.
Making a completely bullet-proof pincf
(post-incf
) is not entirely trivial. (incf i)
has the nice property that i
is evaluated only once. We would like (pincf i)
to also have that property. And so the simple macro falls short:
(defmacro pincf (place &optional (increment 1))
`(prog1 ,place (incf ,place ,increment))
To do this right we have to resort to Lisp's "assignment place analyzer" called get-setf-expansion
to obtain materials that allow our macro to compile the access properly:
(defmacro pincf (place-expression &optional (increment 1) &environment env)
(multiple-value-bind (temp-syms val-forms
store-vars store-form access-form)
(get-setf-expansion place-expression env)
(when (cdr store-vars)
(error "pincf: sorry, cannot increment multiple-value place. extend me!"))
`(multiple-value-bind (,@temp-syms) (values ,@val-forms)
(let ((,(car store-vars) ,access-form))
(prog1 ,(car store-vars)
(incf ,(car store-vars) ,increment)
,store-form)))))
A few tests with CLISP. (Note: expansions relying on materials from get-setf-expansion
may contain implementation-specific code. This doesn't mean our macro isn't portable!)
8]> (macroexpand `(pincf simple))
(LET* ((#:VALUES-12672 (MULTIPLE-VALUE-LIST (VALUES))))
(LET ((#:NEW-12671 SIMPLE))
(PROG1 #:NEW-12671 (INCF #:NEW-12671 1) (SETQ SIMPLE #:NEW-12671)))) ;
T
[9]> (macroexpand `(pincf (fifth list)))
(LET*
((#:VALUES-12675 (MULTIPLE-VALUE-LIST (VALUES LIST)))
(#:G12673 (POP #:VALUES-12675)))
(LET ((#:G12674 (FIFTH #:G12673)))
(PROG1 #:G12674 (INCF #:G12674 1)
(SYSTEM::%RPLACA (CDDDDR #:G12673) #:G12674)))) ;
T
[10]> (macroexpand `(pincf (aref a 42)))
(LET*
((#:VALUES-12679 (MULTIPLE-VALUE-LIST (VALUES A 42)))
(#:G12676 (POP #:VALUES-12679)) (#:G12677 (POP #:VALUES-12679)))
(LET ((#:G12678 (AREF #:G12676 #:G12677)))
(PROG1 #:G12678 (INCF #:G12678 1)
(SYSTEM::STORE #:G12676 #:G12677 #:G12678)))) ;
T
Now here is a key test case. Here, the place contains a side effect: (aref a (incf i))
. This must be evaluated exactly once!
[11]> (macroexpand `(pincf (aref a (incf i))))
(LET*
((#:VALUES-12683 (MULTIPLE-VALUE-LIST (VALUES A (INCF I))))
(#:G12680 (POP #:VALUES-12683)) (#:G12681 (POP #:VALUES-12683)))
(LET ((#:G12682 (AREF #:G12680 #:G12681)))
(PROG1 #:G12682 (INCF #:G12682 1)
(SYSTEM::STORE #:G12680 #:G12681 #:G12682)))) ;
T
So what happens first is that A
and (INCF I)
are evaluated, and become the temporary variables #:G12680
and #:G12681
. The array is accessed and the value is captured in #:G12682
. Then we have our PROG1
which retains that value for return. The value is incremented, and stored back into the array location via CLISP's system::store
function. Note that this store call uses the temporary variables, not the original expressions A
and I
. (INCF I)
appears only once.
回答5:
Semantically, the prefix operators ++ and -- in a language like c++ or whatever are equivalent incf/decf in common lisp. If you realize this and, like your (incorrect) macro, are actually looking for a syntactic change then you've already been shown how to do it with backticks like `(incf ,x). You've even been shown how to make the reader hack around this to get something closer to non-lisp syntax. That's the rub though, as neither of these things is a good idea. In general, non idiomatic coding to make a language resemble another more closely just doesn't turn out to be such a good idea.
However, if are actually looking for the semantics, you've already got the prefix versions as noted but the postfix versions aren't going to be easy to match syntactically. You could do it with enough reader hackery, but it wouldn't be pretty.
If that's what you're looking for, I'd suggest a) stick with incf/decf names since they are idiomatic and work well and b) write post-incf, post-decf versions, e.g (defmacro post-incf (x) `(prog1 ,x (incf ,x)) kinds of things.
Personally, I don't see how this would be particularly useful but ymmv.
回答6:
For pre-increment, there's already incf, but you can define your own with
(define-modify-macro my-incf () 1+)
For post-increment, you could use this (from fare-utils):
(defmacro define-values-post-modify-macro (name val-vars lambda-list function)
"Multiple-values variant on define-modify macro, to yield pre-modification values"
(let ((env (gensym "ENV")))
`(defmacro ,name (,@val-vars ,@lambda-list &environment ,env)
(multiple-value-bind (vars vals store-vars writer-form reader-form)
(get-setf-expansion `(values ,,@val-vars) ,env)
(let ((val-temps (mapcar #'(lambda (temp) (gensym (symbol-name temp)))
',val-vars)))
`(let* (,@(mapcar #'list vars vals)
,@store-vars)
(multiple-value-bind ,val-temps ,reader-form
(multiple-value-setq ,store-vars
(,',function ,@val-temps ,,@lambda-list))
,writer-form
(values ,@val-temps))))))))
(defmacro define-post-modify-macro (name lambda-list function)
"Variant on define-modify-macro, to yield pre-modification values"
`(define-values-post-modify-macro ,name (,(gensym)) ,lambda-list ,function))
(define-post-modify-macro post-incf () 1+)
回答7:
Altough I would definitely keep in mind the remarks and heads-up that simon comments in his post, I really think that user10029's approach is still worth a try, so, just for fun, I tried to combine it with the accepted answer to make the ++x operator work (that is, increment the value of x in 1). Give it a try!
Explanation: Good old SBCL wouldn't compile his version because the '+' symbol must be explicitly set on the dispatch-char lookup table with make-dispatch-macro-character
, and the macro is still needed to pass over the name of the variable before evaluating it. So this should do the job:
(defmacro increment (variable)
"The accepted answer"
`(incf ,variable))
(make-dispatch-macro-character #\+) ; make the dispatcher grab '+'
(defun |inc-reader| (stream subchar arg)
"sets ++<NUM> as an alias for (incf <NUM>).
Example: (setf x 1233.56) =>1233.56
++x => 1234.56
x => 1234.56"
(declare (ignore subchar arg))
(list 'increment (read stream t nil t)))
(set-dispatch-macro-character #\+ #\+ #'|inc-reader|)
See |inc-reader|
's docstring for an usage example. The (closely) related documentation can be found here:
- http://clhs.lisp.se/Body/f_set__1.htm
- http://clhs.lisp.se/Body/f_mk_dis.htm#make-dispatch-macro-character
This implementation has as consequence that number entries like +123 are no longer understood (the debugger jumps in with no dispatch function defined for #\Newline
) but further workaround (or even avoiding) seems reasonable: if you still want to stick with this, maybe the best choice is not to take ++ as prefix, but ## or any other more DSL-ish solution
cheers!
Andres
回答8:
This should do the trick, however I'm not a lisp guru.
(defmacro ++ (variable)
`(setq ,variable (+ ,variable 1)))