I'm trying to write a macro that will define a function exactly once where I don't really care what the name of the function is. My thought was to generate a symbol, use defun with that, and then put a special property on the symbol. The next time the macro is called, it looks for a symbol with this property first and generates a new symbol if it can't find one.

Here's my code:

(require 'dash)
(require 'cl-lib)

(defun lispdoc-find-handler (key)
  (--find (and (functionp it)
               (-> (symbol-plist it)
                   (plist-get 'lispdoc-section-handler)
                   (eq key)))
          (append obarray nil)))

(defmacro lispdoc-defsechandler (key &rest body)
  (let ((sym (or (lispdoc-find-handler key)   ;; use existing symbol if defined
    `(progn (defun ,sym ,@body)
            (put ',sym 'lispdoc-section-handler ,key)
            (cons ,key ',sym))))

;; test

(lispdoc-defsechandler :test (examples)
  (prin1-to-string examples))

(when-let ((f (lispdoc-find-handler :test)))
  (funcall f '(1 2 3 4 5)))

When I eval-defun my call to lispdoc-defsechandler, I get a different symbol every time (where I'm very clearly checking to see if it already exists first). When I macroexpand-last-sexp it, I get the following:

(progn (defun G564 (meta examples)
         (prin1-to-string examples))
  (put 'G564 'lispdoc-section-handler :test)
  (cons :test 'G564))

Evaluating this gives me the same ultimate return value (i.e., (:test . G564)), but now the macro always gives the same result, too (i.e., the function has actually been defined).

Even if I minimize the macro to just the following

(defmacro lispdoc-defsechandler (key &rest body)
  (let ((sym (or (lispdoc-find-handler key)
    `(defun ,sym ,@body)))

I still get the generated sym as a return value, but its function-value isn't defined. What's going on here? If the function isn't being defined, how is it getting evaluated at all?

  • 1
    Set print-circle and print-gensym to see the real result of macro expansion.
    – npostavs
    Mar 6 '17 at 4:12

My crystal ball tells me you're confused by the fact the the generated symbols are not interned: while the symbol's name is G564 it is not the same symbol you get if you (read-from-string "G564") because it is not added to obarray.

Maybe the simplest fix is to (put ',key 'lispdoc-section-handler ,sym) so you can just check (get key 'lispdoc-section-handler) rather than having to loop over the obarray.

  • Alright, so all that needed to happen was to replace (cl-gensym) with (intern (symbol-name (cl-gensym)))? Mar 6 '17 at 4:24
  • 1
    That's one way. Another would be to (put ',key 'lispdoc-section-handler ,sym) so you can just check (get key 'lispdoc-section-handler) rather than having to loop over the obarray.
    – Stefan
    Mar 6 '17 at 6:31
  • Also, you can't iterate over an obarray like its just a vector, because internally its a hash-map. Use mapatoms instead.
    – politza
    Mar 6 '17 at 6:52
  • Hot diggity, why didn't I think of that. And @politza, is there any way to break early from mapatoms? It doesn't really matter anymore, but if there's a way, it'd be good to know :) Mar 6 '17 at 13:13
  • 1
    To break early from any loop, use catch and throw.
    – Stefan
    Mar 6 '17 at 15:31

For added clarity...

The printed representation of any symbol is its name, regardless of whether the symbol is interned. Hence the printed representations of any two symbols with the same name are indistinguishable.

When Emacs reads a printed representation of your macro expansion, the lisp reader naturally interns the symbol names, even though the symbols in question could have been uninterned in the original evaluation.

So when you are dealing with an object containing uninterned symbols, obtaining its printed representation and reading that representation back into lisp objects results in a distinctly different form to the one you started with.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.