bulkf » Description

Modify Macro bulkf update-function-form &rest mode-markers-and-itemsresults

bulkf allows mass updating of places.

update-function-form is evaluated first to produce update-function. The arguments and return values of this function depend on mode-markers-and-items and are described below.

mode-markers-and-items is a list of mode-markers and items to be processed from left to right at macroexpansion-time. A mode-marker is one of the symbols :access, :read, :write or :pass. Any other form is an item. Whenever a mode-marker is encountered, the mode with that name becomes the current mode and remains so until the next mode-marker. The current mode at the start of mode-markers-and-items is :access mode. There are 4 different types of items, corresponding to the 4 different modes that can be the current mode at the time the item is encountered. Here are the semantics of each type of item:


item is a place that will be both read from and written to. At runtime, the subforms of the place are evaluated and the place is read. The primary value is contributed as an additional argument to update-function. update-function also returns an additional value that will be written back into the place (reusing the temporary variables bound to the results of the subforms).


item is a place that will be read from. At runtime, the subforms of the place are evaluated and the place is read. The primary value is contributed as an additional argument to update-function.


item is a place that will be written to. update-function returns an additional value that will be written into the place. The evaluation of the subforms of the place happens at the same time as it would have happened if the place had been read from.


item is a form to be evaluated normally. Its primary value is passed as an additional argument to update-function.

If update-function returns more values than there are places to write to (:access and :write items), the additional values are ignored. If it returns less values than there are of these places, the remaining ones are set to nil. bulkf returns the values that were written into these places. This might be more or less values than were returned by update-function. If a place to be written to has more than one store variable, the remaining such variables are set to nil prior to evaluation of the storing form.

bulkf accepts an optional unevaluated argument before update-function-form (as very first argument). This must be the symbol funcall or apply and determines which operator will be used to call the update-function with its arguments. The default is funcall, which is expected to be used an overwhelming majority of the time. This is the reason this argument has not been made a normal required parameter.

bulkf » Examples

bulkf is very versatile and can be used to easily implement many different types of modify macros. Here are just a few examples:

(defun bulkf-transfer (quantity source destination)
  (values (- source quantity)
          (+ destination quantity)))

(defmacro transferf (quantity source destination)
  `(bulkf #'bulkf-transfer
          :pass ,quantity
          :access ,source ,destination))

(let ((account-amounts (list 35 90)))
  (multiple-value-call #'values
    (transferf 10
               (first account-amounts)
               (second account-amounts))
⇒ 25, 100, (25 100)
(defun bulkf-init (value number-of-places)
  (values-list (make-list number-of-places
                          :initial-element value)))

(defmacro initf (value &rest places)
  `(bulkf #'bulkf-init
          :pass ,value ,(length places)
          :write ,@places))

(let (a b (c (make-list 3 :initial-element nil)))
  (initf 0 a b (second c))
  (values a b c))
⇒ 0, 0, (NIL 0 NIL)
(defun bulkf-spread (spread-function sum-function
                     &rest place-values)
   (let ((number-of-places (length place-values)))
     (make-list number-of-places
                (funcall spread-function
                         (apply sum-function place-values)

(defmacro spreadf (spread-function sum-function &rest places)
  `(bulkf #'bulkf-spread :pass ,spread-function ,sum-function
          :access ,@places))

(let ((a 5) (b (list 10 18 20)))
  (spreadf #'/ #'+ a (first b) (second b))
  (values a b))
⇒ 11, (11 11 20)

(let ((a 2) (b (list 2 4 8)))
  (spreadf #'* #'* a (first b) (second b) (third b))
  (values a b))
⇒ 512, (512, 512, 512)
(defun bulkf-map (function &rest place-values)
  (values-list (mapcar function place-values)))

(defmacro mapf (function &rest places)
  `(bulkf #'bulkf-map :pass ,function :access ,@places))

(let ((a 0) (b 5) (c (list 10 15)))
  (values (multiple-value-list (mapf #'1+ a b (second c)))
          a b c))
⇒ (1 6 16), 1, 6, (10 16)
(defun bulkf-steal (sum-function steal-function
                    initial-assets &rest target-assets)
  (let (stolen leftovers)
    (mapc (lambda (assets)
            (multiple-value-bind (steal leftover)
                (funcall steal-function assets)
              (push steal stolen)
              (push leftover leftovers)))
     (cons (apply sum-function
                  (cons initial-assets (nreverse stolen)))
           (nreverse leftovers)))))

(defmacro stealf (sum-function steal-function hideout &rest targets)
  `(bulkf #'bulkf-steal :pass ,sum-function ,steal-function
          :access ,hideout ,@targets))

(let ((cave :initial-assets)
      (museum '(:paintings :collection))
      (house 20000)
      (triplex (list :nothing-valuable :random-stuff 400)))
  (stealf #'list
          (lambda (assets)
            (if (eq assets :nothing-valuable)
                (values nil assets)
                (values assets (if (numberp assets) 0 nil))))
          cave museum house (first triplex) (second triplex) (third triplex))
  (values cave museum house triplex))