通用参考 - Common Lisp
您的第一个示例更多地是关于 C/C++ 中 lvalues 的概念,对于 Common Lisp,places。 Generalized references是基于宏扩展的,可以由程序员扩展。
假设您构建了一个 cons 单元 (cons 0 1),假设它绑定到名为 x 的局部变量。一个 cons 单元只是一个带有两个插槽的小结构,具有访问器 car 和 cdr。例如,(car x) 为 0,(cdr x) 为 1。通常,列表是通过链接 cons-cells 构建的,其中 cdr 是子列表。
改变槽的历史方法是调用RPLACA/RPLACD 函数(replace car,replace cdr)。 SETF 扩展机制是一种讨论地点以及如何影响它们的方式。在 cons-cells 的情况下,您有两个编写器函数,其名称分别为 (setf car) 和 (setf cdr);名称实际上是两个元素的列表(这是函数名称不是符号的唯一情况)。
然后,您可以编写 (setf (car x) 2) 来改变 x 使其保持值 2。这通过 setf-expansion 被宏扩展为对 RPLACA 的调用。
其他宏是建立在setf之上的,一般以-f后缀命名,如incf:
(incf (cdr x))
上面增加了 X 的 CDR 的值。
setf 也可用于设置局部变量。
有趣的是机制可以组合;哈希表的访问器是(gethash <key> <table> &optional <default-value>);数组的访问器是(aref <array> ... <subscripts>)。你可以写:
(setf (aref (gethash key table) index)
new-value)
以上内容会改变table中与key关联的数组中位置index的值。
组合是有效的,因为扩展只遍历嵌套的数据结构,直到结构需要修改的地方;例如,如果您将一棵树 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),它抽象了底层的魔力。
约束编程 - Prolog
Prolog 和更普遍的约束编程因允许在多个方向上调用关系而臭名昭著(例如 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)