Неразрушающий набор?

Common Lisp, похоже, идет на все, чтобы предоставить как неразрушающие функции (например, subst & remove), так и деструктивные функции и модифицировать макросы (например, delete & rotatef) для общего использования. Предположительно это делается для эффективной поддержки как функциональных, так и нефункциональных стилей программирования. Но также, кажется, есть определенный уклон к нефункциональному стилю в дизайне вездесущего setf, setf макрос, включающий обобщенную ссылку, по-видимому, достаточно гибок, чтобы изменять любое определяемое место (за исключением, возможно, неизменяемых целых чисел и символов). Эта сила, вероятно, объясняет его широкое использование, несмотря на его нефункциональное / разрушительное поведение.

Вопрос в том, почему нет соответствующего "функционального стиля" неразрушающего оператора, созданного по образцу setf (назовите это положено, так как set уже занято), аналогично другим парам неразрушающего / деструктивного оператора lisp. Такой оператор, вероятно, будет принимать те же аргументы, место и значение, но будет возвращать копию объекта, в который место было встроено, вместо нового значения в этом месте. Это также, вероятно, потребовало бы универсального копира некоторого вида, со стандартным setf просто изменив копию перед возвратом. Тогда неразрушающий оператор может быть использован вместо setf для большинства заданий, с setf зарезервированы для действительно больших объектов. Является ли такой оператор осуществимым (или даже возможным) с учетом (предполагаемого) требования к универсальному копировальному устройству и необходимости извлечения объекта из произвольного места, встроенного в него?

2 ответа

Решение

Также не существует универсального установщика, но с SETF вы должны предоставить его при использовании DEFINE-SETF-EXPANDER. Я полагаю, вы могли бы определить эквивалент линз / функциональных ссылок. Для другого подхода Clojure определяет функцию обновления (заимствованную из других языков, я знаю, что она существует в Prolog), которая также не является универсальной. Библиотека FSET предоставляет некоторые функциональные структуры, в частности ассоциативные карты, которые работают так же, как в Clojure.

Предположим, вы ограничиваете себя структурами:

(defstruct foo a b c)
(defstruct bar x y z)

(defparameter *test*
  (make-foo :a (make-bar :x 0 :y 0 :z 0)
            :b 100
            :c "string"))

;; *test* is now
;; #S(FOO :A #S(BAR :X 0 :Y 0 :Z 0) :B 100 :C "string")

Следующий подход делает копию, она опирается на copy-structure а также slot-value, который, хотя и не является стандартным, хорошо поддерживается:

(defun update (structure keys value)
  (if (endp keys)
      value
      (destructuring-bind (key &rest keys) keys
        (let ((copy (copy-structure structure)))
          (setf (slot-value copy key)
                (update (slot-value copy key)
                         keys
                         value))
          copy))))

Вы можете обновить *test* следующее:

(update *test* '(a z) 10)

Вот след:

0: (UPDATE #S(FOO :A #S(BAR :X 0 :Y 0 :Z 0) :B 100 :C "string") (A Z) 10)
  1: (UPDATE #S(BAR :X 0 :Y 0 :Z 0) (Z) 10)
    2: (UPDATE 0 NIL 10)
    2: UPDATE returned 10
  1: UPDATE returned #S(BAR :X 0 :Y 0 :Z 10)
0: UPDATE returned #S(FOO :A #S(BAR :X 0 :Y 0 :Z 10) :B 100 :C "string")

Чтобы обобщить, вы можете принять функцию вместо значения, чтобы вы могли реализовать эквивалент incf частично применяя функцию обновления с #'1+ (результирующее закрытие примет список ключей).

Кроме того, вам необходимо обобщить операцию копирования, что возможно с помощью универсальных функций. Кроме того, вы можете использовать другие виды аксессуаров и заменить slot-value с общим access функция (для которой есть (setf access) операция). Однако этот общий подход copy/setf может быть расточительным, если вы хотите поделиться некоторыми данными. Например, вам нужно всего лишь скопировать часть списка, которая ведет к вашим данным, а не те ячейки, которые следуют за ними.

Вы можете определить некоторые средства для определения пользовательских обновлений:

(defmethod perform-update (new (list list) position)
  (nconc (subseq list 0 position)
         (list new)
         (nthcdr (1+ position) list)))

Common Lisp не имеет универсального копира по той же причине, по которой он не имеет встроенного печатаемого представления объектов CLOS (см., Например, Сохранение объектов CLOS): мощь MOP.

В частности, создание объекта может иметь произвольные побочные эффекты, репликацию которых трудно гарантировать. Например, определить initialize-instance для вашего класса, чтобы изменить слоты на основе чего-то плавного (например, приливы или просто random). Тогда результат make-instance Вызванный с одинаковыми аргументами дважды может отличаться. Вы можете утверждать, что это аргумент в пользу memcpy стиль универсального копира. Тем не менее, представьте себе класс, который выполняет учет экземпляров (:allocation :class слот, который содержит хеш-таблицу, сопоставляющую уникальный идентификатор экземпляру). Теперь в оба конца от скопированного объекта через таблицу будет получен другой объект (оригинал, а не копия) - серьезное нарушение договора. И эти примеры - лишь вершина айсберга.

Однако я бы не согласился с тем, что Common Lisp поощряет императивный стиль больше, чем функциональный стиль. Это просто обескураживает стиль, который вы описываете (копировать + setf). Подумайте об этом так: от функционального POV копия неотличима от оригинала (потому что все неизменно).

Есть также "маленькие подсказки", показывающие явное предпочтение функциональному стилю. Обратите внимание, что "естественные" имена (например, append) зарезервированы для неразрушающих функций; разрушительные версии (например, nconc) даны неизвестные имена.

Кроме того, имена путей, символы и числа (включая такие составные ratio а также complex) являются неизменяемыми, т. е. все функции pathname и number создают новые объекты (функциональный стиль).

Таким образом, IMO, Common Lisp поощряет программистов использовать функциональный стиль, в то же время делая императивный стиль выполнимым, в соответствии с лозунгом " простые вещи должны быть легкими, а сложные должны быть возможными ".

Смотрите также рецензию Кента Питмана.

Другие вопросы по тегам