Or with more Python-esque syntax:
(let ((files (coerce "ABCDEFGH" 'list))
(ranks (loop for r from 1 to 9 collect r)))
[(format nil "~a~a" file rank) (file <- files) (rank <- ranks)])
Based on the list comprehension macro from 1991 in
https://3e8.org/pub/scheme/doc/lisp-pointers/v4i2/p16-lapalm... that still works.
(defmacro comp ((e &rest qs) l2)
(if (null qs) `(cons ,e ,l2) ; rule A
(let ((q1 (car qs))
(q (cdr qs)))
(if (not (eq (cadr q1) '<-)) ; a generator?
`(if ,q1 (comp (,e ,@q) ,l2) ,l2) ; rule B
(let ((v (car q1)) ; rule C
(l1 (third q1))
(h (gentemp "H-"))
(us (gentemp "US-"))
(us1 (gentemp "US1-")))
`(labels ((,h (,us) ; corresponds to a letrec
(if (null ,us) ,l2
(let ((,v (car ,us))
(,us1 (cdr ,us)))
(comp (,e ,@q) (,h ,us1))))))
(,h ,l1)))))))
(defun open-bracket (stream ch)
(do ((l nil)
(c (read stream t nil t)(read stream t nil t)))
((eq c '|]|) `(comp ,(reverse l) ()))
(push c l)))
(defun closing-bracket (stream ch) '|]|)
(set-macro-character #\[ #'open-bracket)
(set-macro-character #\] #'closing-bracket)
Supports filtering too, e.g.
(let ((xs '(1 2 3 4))
(ys '(1 2 3 4)))
[(+ x y) (x <- xs) (y <- ys) (evenp x) (oddp y)])
; -> (3 5 5 7)