7.9 Перечисление элементов структур данных и побочные эффекты

В момент выполнения некоторых системных операций (функций, операторов), в которых может вызываться пользовательский код (например, при использовании sort), для этого кода запрещены побочные эффекты (действия).

Рассмотрим следующий пример:

(let ((x ’(apples peaches pumpkin pie)))
  (dolist (z x)
    (when (eq z ’peaches)
      (setf (cddr x) ’(mango kumquat)))
    (format t " S " (car z))))

В зависимости от особенностей реализации dolist, данный код может просто вывести

apples peaches mango kumquat

(что скорее всего и ожидается), но может также вывести и

apples peaches pumpkin pie

Вот вероятная реализация dolist для первого варианта

(defmacro dolist ((var listform &optional (resultform ”nil))
                  &body body)
  (let ((tailvar (gensym "DOLIST")))
    ‘(do ((,tailvar ,listform (cdr ,tailvar)))
         ((null ,tailvar) ,resultform)
       (let ((,var (car ,tailvar))) ,@body))

А вот вероятная реализация dolist для второго варианта

(defmacro dolist ((var listform &optional (resultform ”nil))
                  &body body)
  (let ((tailvar (gensym "DOLIST")))
    ‘(do ((,tailvar ,listform))
         ((null ,tailvar) ,resultform)
       (let ((,var (pop ,tailvar))) ,@body))

В общем случае, деструктивная модификация структуры, которая может нарушить порядок перечисления элементов, запрещена. Частные случаи перечислены ниже.

Для операций перечисления элементов списка, cdr цепочка не может быть деструктивно модифицирована.

Для операций, которые перечисляют элементы массива, данный массив не может быть расширен (смотрите adjust-array) и, при наличии, не может быть изменен указатель заполнения.

Для операций перечисления элементов хеш-таблицы (таких как with-hash-table-iterator и maphash, в пользовательком коде не могут добавляться в данную таблицу новые элементы.

Для операций перечисления символов пакета (например, with-package-iterator и do-symbols) в пакет не могут интернироваться, дезинтернироваться новые символы, которые относятся к данному пакету. Однако текущий обрабатываемый символ может быть дезинтернирован.

Деструктивные операции, такие как delete, требуют более жёстких ограничений и эти ограничения описаны отельно в каждой функции.

Таблица 7.1 содержит большинство операций и функций запрещающих побочные эффекты в пользовательском коде, которые в них передаются в качестве тела для исполнения или в виде функционального объекта.

Кроме того, следует отметить, что пользовательский код не должен модифицировать структуру списка, которая интерпретируется вычислителем, который был вызван неявно или явно с помощью eval. Например, как в случае функции-ловушки (функция вывода defstruct, значение *evalhook* или *applyhook*, и так далее), которая является замыканием интерпретируемого кода. Также, функции вывода defstruct и другие ловушки не должны выполнять побочные действия над выводимыми структурами, или над строками переданными в make-string-input-stream. В целом, будте бдительны.

Следует отметить, что такие операции как mapcar или dolist работают не только с cdr указателями (при прохождении к концу списка), но также и с car указателями (при обработке непосредственно элемента). Ограничения побочных эффектов относятся и к одному, и ко второму указателям.



Таблица 7.1: Операции не допускающие побочных действий в пользовательском коде
adjoin  maphash reduce
assoc  mapl remove
assoc-if  maplist remove-duplicates
assoc-if-not  member remove-if
count  member-if remove-if-not
count-if  member-if-not search
count-if-not  merge set-difference
delete  mismatch set-exclusive-or
delete-duplicates  nintersection some
delete-if  notany sort
delete-if-not  notevery stable-sort
do-all-symbols  nset-difference sublis
do-external-symbols  nset-exclusive-or subsetp
do-symbols  nsublis subst
dolist  nsubst subst-if
eval  nsubst-if subst-if-not
every  nsubst-if-not substitute
find  nsubstitute substitute-if
find-if  nsubstitute-if substitute-if-not
find-if-not  nsubstitute-if-nottree-equal
intersection  nunion union
некоторые loop выражения position with-hash-table-iterator
map  position-if with-input-from-string
mapc  position-if-not with-output-to-string
mapcan  rassoc with-package-iterator
mapcar  rassoc-if
mapcon  rassoc-if-not