3

Consider the following cl-loop:

(cl-loop for x across "abc"
         for y across "123"
         concat (string x y))

This evaluates to "a1b2c3", i.e. it zips both strings together. However, if I look at the expansion of cl-loop it seems like that should not be possible, as

(pp-macroexpand-expression
 '(cl-loop for x across "abc"
           for y across "123"
           concat (string x y)))

expands to (comments by me)

(cl-block nil
  (let*
      ((--cl-vec-- "abc")      ; --cl-vec-- set to "abc"
       (--cl-idx-- -1)
       (x nil)
       (--cl-vec-- "123")      ; --cl-vec-- set again, this time to "123"
       (--cl-idx-- -1)
       (y nil)
       (--cl-var-- ""))
    (while
        (and
         (setq --cl-idx--
               (1+ --cl-idx--))
         (< --cl-idx--
            (length --cl-vec--))
         (progn
           (setq x
                 (aref --cl-vec-- --cl-idx--))  ; A
           (setq --cl-idx--
                 (1+ --cl-idx--)))
         (< --cl-idx--                          ; C
            (length --cl-vec--)))              
      (setq y
            (aref --cl-vec-- --cl-idx--))       ; B
      (cl-callf concat --cl-var--
        (string x y)))
    --cl-var--))

However, when I try to evaluate that it only returns "12", which is expected, since x will refer to "1" at A and y will refer to Bat A, and then --cl-idx-- will be 4 in the next iteration C and thus end the loop.

That's not how the original code can work, so cl-loop cannot expand into that form. How can I get the actual macro expansion with proper identifiers?

1

2 Answers 2

3

As phils pointed out, the reason is that those symbols are uninterned. You can set print-gensym and print-circle to t to get uninterned symbols to print correctly. Once that is done, the expansion becomes

(cl-block nil
  (let*
      ((#2=#:--cl-vec-- "abc")
       (#1=#:--cl-idx-- -1)
       (x nil)
       (#4=#:--cl-vec-- "123")
       (#3=#:--cl-idx-- -1)
       (y nil)
       (#5=#:--cl-var-- ""))
    (while
    (and
     (setq #1#
           (1+ #1#))
     (< #1#
        (length #2#))
     (progn
       (setq x
         (aref #2# #1#))
       (setq #3#
         (1+ #3#)))
     (< #3#
        (length #4#)))
      (setq y
        (aref #4# #3#))
      (cl-callf concat #5#
    (string x y)))
    #5#))

which correctly evaluates to "a1b2c3".

1
  • Ah, so my call above should be wrapped around with (let ((print-gensym t) (print-circle t)) (pp-macroexpand-expression ...)). That's good to know, thanks!
    – Zeta
    Commented Aug 18, 2022 at 10:17
1

The --cl-vec-- and --cl-idx-- symbols (and other similarly-named symbols, should you see any) are uninterned, and there may be more than one of each.

You're only seeing the printed representation of those symbols, and consequently reading that code does not produce something equivalent to the original code, as the symbols being read that way will get interned and clash in a way that did not originally happen.

I think to get readable code, you would need to replace all make-symbol instances with gensym or cl-gensym in the relevant macro and function definitions. (Edit: @d125q has shown how to achieve this without modifying anything in cl-lib.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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