[alexandria-devel] Possible WITH-COLLECTOR/COLLECTORS macros

Faré fahree at gmail.com
Sun Mar 7 17:54:22 UTC 2010


CLISP has the similar EXT:WITH-COLLECT.
ASDF 1.630 has a similar WHILE-COLLECTING (taken from FARE-UTILS)
with identical specification:

(defmacro while-collecting ((&rest collectors) &body body)
  (let ((vars (mapcar #'(lambda (x) (gensym (symbol-name x))) collectors))
        (initial-values (mapcar (constantly nil) collectors)))
    `(let ,(mapcar #'list vars initial-values)
       (flet ,(mapcar #'(lambda (c v) `(,c (x) (push x ,v))) collectors vars)
         , at body
         (values ,@(mapcar #'(lambda (v) `(nreverse ,v)) vars))))))

At ITA, we use a slightly more elaborate variant
that allows to peek at the list being created
(which is admittedly seldom used).
I copied the relevant section of our quux/macros.lisp below.

[ François-René ÐVB Rideau | Reflection&Cybernethics | http://fare.tunes.org ]
Resentment is like taking poison and waiting for the other person to die.
	— Malachy McCourt

(defmacro with-collectors ((&rest collection-descriptions)
			   &body body)

  "COLLECTION-DESCRIPTIONS is a list of clauses, each of which is
   (VARIABLE FUNCTION).  The body can call FUNCTION on an argument
   to add that value to the end of a list kept in the value of VARIABLE.
   FUNCTION runs in constant time, regardless of the length of the list.
   Alternatively, a clause can be (PLACE FUNCTION :FREE T), in which
   case no variable is bound, and FUNCTION (destructively) adds the value
   to the list already stored in PLACE."

  (let ((let-bindings nil)
        (flet-bindings nil)
        (object '#:OBJECT)
        (dynamic-extent-fns nil))
    (dolist (collection-description collection-descriptions)
      (destructuring-bind (collection-place collector-name &key free elem-type)
          collection-description
        (let ((tail-name (make-symbol (format nil "~A-TAIL" collection-place))))
          (unless free
            (assert (not (listp collection-place)) ()
                    "Unless it is free, the collection name must be a
symbol, not ~A~%"
                    collection-place)
            (push collector-name dynamic-extent-fns))
          (setq let-bindings
                (nconc let-bindings
                       `(,@(unless free `((,collection-place nil)))
                           (,tail-name ,@(if free
                                           `((last ,collection-place))
                                           `(()))))))
          (setq flet-bindings
                (nconc flet-bindings
                       `((,collector-name (,object)
                           ,@(when elem-type
			       `((check-type ,object ,elem-type)))
			   (setq ,tail-name
				   (if ,tail-name
				     (setf (cdr ,tail-name)  (list ,object))
				     (setf ,collection-place (list ,object)))))))))))
    `(let (, at let-bindings)
       (flet (, at flet-bindings)
         ,@(if dynamic-extent-fns
             `((declare (dynamic-extent ,@(nreverse (loop for fn in
dynamic-extent-fns
                                                          collect `#',fn))))))
         , at body))))

(defmacro with-unique-collectors ((&key (test '#'eql)) (&rest
collection-descriptions)
				  &body body)

  "COLLECTION-DESCRIPTIONS is a list of clauses, each of which is
   (VARIABLE FUNCTION).  The body can call FUNCTION on one argument
   to add that value to the end of a list kept in the value of VARIABLE.
   FUNCTION runs in constant time, regardless of the length of the list.
   Alternatively, a clause can be (PLACE FUNCTION :FREE T), in which
   case no variable is bound, and FUNCTION adds the value to the list
   already stored in PLACE.

   This collects only a single occurrence of each object, using TEST
   to test the equality."

  (let ((let-bindings nil)
        (flet-bindings nil)
        (object '#:OBJECT)
        (dynamic-extent-fns nil))
    (dolist (collection-description collection-descriptions)
      (destructuring-bind (collection-place collector-name &key free elem-type)
          collection-description
        (let ((tail-name (make-symbol (format nil "~A-TAIL" collection-place))))
          (unless free
            (assert (not (listp collection-place)) ()
                    "Unless it is free, the collection name must be a
symbol, not ~A~%"
                    collection-place)
            (push collector-name dynamic-extent-fns))
          (setq let-bindings
                (nconc let-bindings
                       `(,@(unless free `((,collection-place nil)))
                           (,tail-name ,@(if free
                                           `((last ,collection-place))
                                           `(()))))))
          (setq flet-bindings
                (nconc flet-bindings
                       `((,collector-name (,object)
                           ,@(when elem-type `((check-type ,object ,elem-type)))
			   (unless (member ,object ,collection-place :test ,test)
			     (setq ,tail-name (if ,tail-name
						(setf (cdr ,tail-name) (list ,object))
						(setf ,collection-place (list ,object))))))))))))
    `(let (, at let-bindings)
       (flet (, at flet-bindings)
         ,@(if dynamic-extent-fns
             `((declare (dynamic-extent ,@(nreverse (loop for fn in
dynamic-extent-fns
                                                          collect `#',fn))))))
         , at body))))

;; provides the same semantics as the macro WITH-COLLECT from GNU clisp.
;; See also with-collectors above if you need more control.
(defmacro with-collected-results ((&rest collectors) &body body)
  (loop for c in collectors
        for l = (gensym "LIST")
        collect l into collections
        collect (list l c) into collection-descriptions
        finally (return `(with-collectors ,collection-descriptions
                           , at body (values , at collections)))))

(defmacro collected-values (&rest collect-descriptions)
  "Each collect-description is (a b).
   For each collect-description, if a is non-null collect b."
  (with-gensyms (coll)
    `(with-collected-results (,coll)
       ,@(mapcar #'(lambda (c)
                     `(when ,(first c)
                        (,coll ,(second c))))
                 collect-descriptions))))




More information about the alexandria-devel mailing list