# 引用 引用(ref)是 Deft 的可变状态原语:一个保存单个值的单元格, 用 `deref` 读取,用 `reset!` 或 `swap!` 更新。其他所有集合都是 持久化的——操作返回新值——所以引用是表达“这一处可能会变”的 方式:计数器、缓存,以及 TUI 应用携带的模块级状态映射。这些 裸函数被编译进每个运行时,无需导入;关于在协程之间共享它们的 调度器,参见并发。 有两种作用域不同、接口相同(`deref`、`reset!`、`swap!`、 `add-watch`、`remove-watch`)的引用: - 匿名引用(`[ref v]`)——按值作用域的单元格:每次调用创建, 可传递、可存入数据结构,丢弃后由 GC 回收。 - 绑定单元(`[var x]`)——模块 `def` 背后的单元格;在表达式中 `$x` 会自动解引用。`def` 重新绑定只写根、不通知观察器;只有通过 该单元格显式 `reset!`/`swap!` 才会触发。绑定单元打印为 `#<var name>`(匿名引用为 `#<ref …>`)。 同一机制,两种作用域:`def x 1` 是对命名单元格的静默 `reset!` (首次定义时创建),`$x` 是静默的 `deref`。匿名引用是以值而非名字 到达的同一种单元格,因此需要显式动词,并获得可寻址性——无需导入 定义模块即可传递、按实例各持一个、嵌入数据结构。观察器对两者都可用; `[var x]` 只是把命名单元格交给你。 ## 函数 | 函数 | 用途 | |---|---| | `ref value` | 创建保存 `value` 的匿名引用 | | `deref r` | 读取当前值(引用或绑定单元) | | `ref? x` | 当 `x` 是引用单元格(任一作用域)时为真 | | `var? x` | 当 `x` 是绑定单元(`[var x]`)时为真 | | `reset! r value` | 存储 `value`、返回它并触发观察器 | | `swap! r f args...` | 调用 `(f current args...)`,存储并返回结果 | | `add-watch r key fn` | 在 `key` 下注册观察器 `fn` | | `remove-watch r key` | 移除 `key` 下的观察器 | `swap!` 以 `(f current args...)` 形式调用函数——先是当前值,然后是 任何额外参数——并存储返回值。`reset!` 完全忽略旧值。两者都会返回 新值,并按注册顺序同步触发每个已注册的观察器,参数为 `(key cell old new)`;每次调用都会触发观察器,即使新值等于旧值。 `incr` 能识别引用:作用于持有引用的槽位时,它会递增引用的载荷并 触发观察器——`[incr counter]` 就是计数器惯用法,等价于 `+1` 的 `swap!`。 引用按身份而非内容比较:两个都保存 `%{}` 的引用仍是不同的值。 对非引用调用 `deref` 返回 `nil`,因此当值的类型没有保证时请使用 `ref?`/`var?`。`add-watch` 和 `remove-watch` 返回 `nil`;在已有 键下添加会替换该观察器。 引用在一个运行时内按引用共享,因此该运行时上的协程能看到彼此的 更新。不过它们并非只用于并发代码:当你想要一个具有稳定身份的 可变单元格,或者想要一个挂载 `add-watch` 反应的地方时,都可以 使用引用。Deft 的调度器是协作式的——协程只在显式让出点 (`yield`、`await`、`sleep`)切换——所以单独的读或写不会在中途 被抢占,不挂起的读-改-写是原子的。 注意——该 API 借用了 Clojure 的 `atom` 词汇(`deref`、`swap!`、 `reset!`、`add-watch`),但语义不同: - 没有暴露的 `compare-and-set!`,也不支持验证器。 - 传给 `swap!` 的更新闭包不得挂起(`await`/`sleep`/`yield`): 挂起的闭包会让另一个协程在此期间更新单元格,恢复后的写入是 后写者获胜——没有比较并重试。请保持更新函数为纯函数且不挂起。 - 引用不是跨运行时的原语——不同的运行时通过 `pub/*` 或 `rpc` 通信(参见并发)。 ## 示例
def counter [ref 0]

[deref $counter]                            # => 0
[reset! $counter 5]                         # => 5
[swap! $counter {|n| ($n + 1)}]             # => 6
[incr counter]                              # => 7(识别引用的 +1)

[ref? $counter]                             # => true
[ref? 42]                                   # => false
额外的 `swap!` 参数追加在当前值之后:
[swap! $counter {|n step| ($n + $step)} 10] # => 17
状态映射模式把相关字段保存在一个引用中;更新通过 `swap!`, 读取通过 `deref`:
def state [ref %{ :count 0 :label "Clicks" }]

def bump {||
    [swap! $state {|s| [assoc $s :count ($s~count + 1)]}]
}

[bump]
[bump]
echo [deref $state]~count                   # => 2
观察器观察每次更新——适用于持久化、日志记录或使缓存失效:
def a [ref 0]

[add-watch $a :log {|k cell old new|
    echo "change:" $old "->" $new
}]

[reset! $a 5]                               # prints: change: 0 -> 5
[remove-watch $a :log]
[reset! $a 9]                               # no output
绑定单元同样可以观察——但只有显式变更会触发,`def` 不会:
def status "idle"

[add-watch [var status] :log {|k cell old new|
    echo "status:" $old "->" $new
}]

def status "busy"                    # 无输出(def 是静默的根写入)
[reset! [var status] "done"]         # 打印: status: busy -> done
$status                              # => "done"
## 另见 - Set 与 Def — 绑定单元、`[var x]` 与重定义语义 - 并发 — `deftask`、`defcoro` 和按运行时的状态 - 类型与协议 — 值如何分派 - 映射 — 引用通常保存的持久化映射