Обобщенные ссылки - Common Lisp
Ваш первый пример больше о содержании lvalues в C / C ++, а в случае Common Lisp, place . Обобщенные ссылки основаны на макроразложении и могут быть расширены программистом.
Допустим, вы строите cons-ячейку (cons 0 1)
, и предположим, что она связана с локальной переменной с именем x
. Минус ячейка - это просто небольшая структура с двумя слотами, с аксессуарами car
и cdr
Например, (car x)
равно 0, а (cdr x)
равно 1. Как правило, списки строятся с помощью объединения cons-ячеек, где cdr
является подсписком.
Исторический способ изменить слоты - вызвать функции RPLACA/RPLACD
( заменить автомобиль , заменить CDR ). Механизм расширения SETF - это способ рассказать о местах и о том, как на них воздействовать. В случае cons-клеток у вас есть две функции записи, имена которых (setf car)
и (setf cdr)
; имена - это буквально списки двух элементов (это единственный случай, когда имя функции не является символом).
Затем вы можете написать (setf (car x) 2)
, чтобы преобразовать x
, чтобы он содержал значение 2. Это расширение макроса как вызов RPLACA
с помощью setf-extension.
Другие макросы построены поверх setf
и обычно имеют суффикс -f
, например incf
:
(incf (cdr x))
Вышеуказанное увеличивает значение CDR X.
setf
также тривиально работает для установки локальных переменных.
Что интересно, так это механизм, который можно составить; аксессоры для хеш-таблицы (gethash <key> <table> &optional <default-value>)
; аксессор для массивов (aref <array> ... <subscripts>)
. Вы могли бы написать:
(setf (aref (gethash key table) index)
new-value)
И приведенное выше изменит значение в позиции index
в массиве, связанном с key
в table
.
Композиция эффективна, потому что расширение только пересекает вложенные структуры данных до точки, где структура должна быть изменена; Например, если вы изменяете дерево tree
, равное:
(root-node (node-a 0 1) (node-b 2 3))
Тогда значение 2 - это первый дочерний элемент второго дочернего элемента корневого узла, который в терминах позиций списка записывается следующим образом:
(second (third tree))
=> 0
Если вы хотите увеличить это значение, напишите:
(incf (second (third tree)))
И INCF
достаточно умен, чтобы пройти список только один раз; это результат макроразложения:
(LET* ((#:LIST (CDR (THIRD TREE))) (#:NEW (+ 1 (CAR #:LIST))))
(SB-KERNEL:%RPLACA #:LIST #:NEW))
Этот механизм может быть расширен путем вызова define-setf-expander
; Например, библиотека Cells
реализует своего рода механизм распространения ограничений, такой как формула электронных таблиц (поток данных, реактивное программирование), где изменение значения слота объекта распространяется вниз пользователям этого значения слота ( http://stefano.dissegna.me/cells-tutorial.html). Но для пользователей это всего лишь вопрос вызова (setf (slot object) value)
, который абстрагирует магию, лежащую в основе.
Ограничение программирования - Пролог
Пролог и в более общем смысле программирование ограничений печально известны тем, что позволяют вызывать отношения в нескольких направлениях (например, с https://eclipseclp.org/ интерпретатором):
lib(fd).
f(X,R) :- R #= X + 1.
Случай, когда X
- земля, а R
левая переменная:
[eclipse 3]: f(3,R).
R = 4
Yes (0.00s cpu)
Случай, когда X
является переменным, а R
земля:
[eclipse 4]: f(X,4).
X = 3
Yes (0.00s cpu)
Случай с обоими оставленными переменными:
[eclipse 5]: f(X,Y).
X = X{[-10000000 .. 9999999]}
Y = Y{[-9999999 .. 10000000]}
Delayed goals:
-1 - X{[-10000000 .. 9999999]} + Y{[-9999999 .. 10000000]} #= 0
Случай, когда оба заземлены:
[eclipse 6]: f(5,10).
No (0.00s cpu)