Writing a ++ macro in Common Lisp
Asked Answered
M

8

6

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?

Miserere answered 15/9, 2008 at 18:47 Comment(1)
Not a duplicate, but related: Writing a destructive macro or function like incf?Emulation
O
17

Remember that a macro returns an expression to be evaluated. In order to do this, you have to backquote:

(defmacro ++ (variable)
   `(incf ,variable))
Oral answered 15/9, 2008 at 18:52 Comment(0)
A
14

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).

Assent answered 15/9, 2008 at 20:54 Comment(0)
P
11

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.

Puglia answered 12/5, 2012 at 22:13 Comment(1)
@JoshuaTaylor A macro created by define-modify-macro returns the new, updated value. Since that is what incf needs to return, it is easy. It's not obvious whether it's equally easy to write a pincf with define-modify-macro, where the requirement is to return the value which was previously in the place.Puglia
P
10

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)))
Pinguid answered 27/10, 2008 at 23:36 Comment(1)
This evaluates number twice though. Kaz's answer shows how to avoid this.Emulation
B
7

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.

Beira answered 16/9, 2008 at 22:9 Comment(1)
Mentioning prog1 is by itself reason enough for this post. Been using CL for a long time, and had forgotten that it existed a long time ago.Anemophilous
T
5

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+)
Throughcomposed answered 16/9, 2008 at 21:52 Comment(0)
L
2

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:

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

Lavallee answered 20/3, 2016 at 15:25 Comment(0)
S
-2

This should do the trick, however I'm not a lisp guru.

(defmacro ++ (variable)
  `(setq ,variable (+ ,variable 1)))
Sister answered 15/9, 2008 at 18:54 Comment(2)
This won't quite work as expected in all situations. As you put it 'variable' gets evaluated twice which is not what the user expect if the expression has side effects. E.g. look how your macro expands this quite reasonable invocation: (++ (aref some-vector (++ some-index)))Bodgie
This also doesn't work if if variable is anything other than a variable (or a symbol macro), because setq doesn't work with non-variables. E.g., with this, you can't do (++ (car list)).Emulation

© 2022 - 2024 — McMap. All rights reserved.