[parenscript-devel] maybe-once-only

Daniel Gackle danielgackle at gmail.com
Thu Jan 31 04:28:05 UTC 2013

I heartily approve of the introduction of MAYBE-ONCE-ONLY in a5cf0df,
because it drives me nuts (perhaps irrationally) to see superfluous
bindings in generated code. If nothing else, they detract from PS's
goal of producing readable JS.

Upgrading our codebase to use the latest Parenscript from git, I
noticed this macro and saw an opportunity to remove a bit of code
we wrote that does the same thing. Doing so, I noticed the following
four things.

1. MAYBE-ONCE-ONLY wasn't being exported, so I modified package.lisp
to do that. Should "ps" be in the name, in that case? MAYBE-ONCE-ONLY
is a good name (it certainly kicks the name we were using's butt, as
you will see below), but already a little on the verbose side, and
MAYBE-PS-ONCE-ONLY is over my comfort limit in that respect. So I left
the name as is.

2. There is a problem with symbol macros leading to multiple

  (defpsmacro true? (x)
    (maybe-once-only (x)
      `(and (!null ,x) (not (= ,x false)))))

  (define-ps-symbol-macro aaa (bar))

  (defun foo ()
    (symbol-macrolet ((bbb (bar)))
      (when (true? bbb)
    (when (true? aa)

=> function foo() {
    if (bar() != null && bar() !== false) {
    return bar() != null && bar() !== false ? baz() : null;

Note that this is true for both kinds of symbol macro — top-level and
local — as the example shows.

3. The generated code evaluates bound expressions in reverse order of
appearance in the code. I don't know if this would ever be a problem,
but the principle of least surprise probably cautions against it. That
is, in the following form, (bar) should probably be evaluated before (baz):

  (defmacro+ps blah (a b)
    (maybe-once-only (a b)
      `(list ,a ,b)))

  (defun foo ()
    (blah (bar) (baz)))

=> function foo() {
    var b3600 = baz();
    var a3599 = bar();
    return [a3599, b3600];

4. Finally, here for comparison purposes is the macro I removed from
our code in order to use MAYBE-ONCE-ONLY instead. It has a worse name,
the horrible ONCE-WHEN (I couldn't come up with anything better), but
I think I prefer its implementation for a couple of reasons: it
doesn't suffer from the two problems (symbol macros and evaluation
order) mentioned above, and it delegates to the classic ONCE-ONLY
(PS-ONCE-ONLY in our case) for all of the macro magic, which makes it
shorter and easier to follow.

  (defun atomic? (expr)
    (or (atom expr) (atom (ps::ps-macroexpand expr))))

  (defmacro once-when (vars &body body)
    (cond ((= (length vars) 1)
           `(cond ((atomic? ,(car vars)) , at body)
                  (t (ps-once-only (,(car vars)) , at body))))
          (t `(once-when (,(car vars))
                (once-when ,(cdr vars)
                  , at body)))))

Perhaps the two implementations can be combined into something better.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mailman.common-lisp.net/pipermail/parenscript-devel/attachments/20130130/56ad973a/attachment.html>

More information about the parenscript-devel mailing list