3

In the dash library I noticed the use of make-symbol to avoid symbol conflicts during macro expansion.

(defmacro --filter (form list)
  "Anaphoric form of `-filter'.
See also: `--remove'."
  (declare (debug (form form)))
  (let ((r (make-symbol "result")))
    `(let (,r)
       (--each ,list (when ,form (!cons it ,r)))
       (nreverse ,r))))

From what I read in common lisp books, gensym is used for things like this to create a unique symbols via.

(let ((r (cl-gensym "result"))) ...)

I'm confused because when I macro expand this kind of form.

(let ((result '(1 2 3 4)))
  (--filter (> 2 it) result))

I get this and it looks like the symbol created with make-symbol named result is indeed conflicting with the result that contains '(1 2 3 4).

In fact, when I copy the result of this macroexpansion and run it myself I get what I expect: nil.

(let ((result '(1 2 3 4)))
  (let
      (result)
    (--each result
      (when
          (> 2 it)
        (!cons it result)))
    (nreverse result)))

But this seems to work. Instead of returning nil as I would expect from the macroexpansion. Why is this?

(let ((result '(1 2 3 4)))
  (--filter (> 2 it) result))
;; => (1)
Drew
  • 75,699
  • 9
  • 109
  • 225
Aquaactress
  • 1,393
  • 8
  • 11

1 Answers1

3

It's not the same symbol, even though it has the same name. This returns (nil foo), because the interned symbol foo created by reading 'foo is not the same symbol as the uninterned symbol foo returned by make-symbol.

(let ((res  (make-symbol "foo"))) (list (eq res 'foo) res))

C-h f make-symbol says:

make-symbol is a built-in function in C source code.

(make-symbol NAME)

Return a newly allocated uninterned symbol whose name is NAME.

Its value is void, and its function definition and property list are nil. This function does not change global state, including the match data.

cl-gensym also creates an uninterned symbol. As the Common Lisp doc for gensym says:

Creates and returns a fresh, uninterned symbol, as if by calling make-symbol. (The only difference between gensym and make-symbol is in how the new-symbol's name is determined.)

The name of the new-symbol is the concatenation of a prefix, which defaults to "G", and a suffix, which is the decimal representation of a number that defaults to the value of *gensym-counter*.

Unfortunately, the Elisp doc for gensym does not include the helpful info about its similarity and difference from make-symbol. The Common Lisp doc is better, in this case.


As for your related question in the comments: "Why would anyone use gensym, since the names are not meaningful?":

  • You can programmatically produce the symbol names with gensym. If you just need a few such symbols (e.g. hand-coding) then you can of course use make-symbol, to have meaningful names.

  • Sometimes you don't care what the names are, or there may be a lot of them - again, especially when the code is generated by program.

The second case is akin to using x1, x2,... and y1, y2,... versus using x, y, z,... (But the numerals need to be unique across the Emacs session.) Another analogy would be GUIDs.

gensym does let you provide a name prefix, and that can be meaningful, e.g., for a group of symbols having something in common.

Drew
  • 75,699
  • 9
  • 109
  • 225
  • Why would we ever use `gensym` over `make-symbol` for macros then? `make-symbol` makes the macro expansion easier to read since it doesn't append numbers to a symbol. – Aquaactress Dec 13 '19 at 21:02
  • Maybe this is a separate question. Lmk if I should ask separately. – Aquaactress Dec 13 '19 at 21:03
  • 1
    (1) You can programmatically produce the symbol names with `gensym`. If you just need a few (hand coding) then you can of course use `make-symbol`, to have meaningful names. (2) Sometimes you don't care what the names are - again, especially when the code is created by program. – Drew Dec 13 '19 at 22:18