# set 与 def
`set` 和 `def` 都会把一个值绑定到名字下,且二者的求值结果都是所绑定的值。它们的区别在于写入哪个作用域:
| | `set` | `def` |
|---|-------|-------|
| 模块作用域下 | 写入模块命名空间 | 写入模块命名空间 |
| 函数内部 | 创建或重新绑定局部变量 | 写入模块命名空间 |
| 具名函数 | 像普通值一样绑定闭包 | `def name {\|params\| ...}` 声明一个函数 |
| 惯用法 | 循环计数器、累加器、临时状态 | 模块常量、配置、共享状态、函数 |
只需记住一条规则:`def` 始终写入模块命名空间;当作用域内存在局部变量时,`set` 写入该局部变量。 在模块作用域下两者一致——都写入命名空间——这也是脚本在顶层可以随意使用其中任何一个的原因。二者的区别只在函数和闭包内部才会显现。
## 模块作用域 —— `def`
模块级绑定对文件中的所有函数和闭包,以及模块的导入者都可见。声明请使用 `def`:
def base-url "https://api.example.com" def retry-limit 3 def url-for {|path| [str $base-url $path]}
`def` 也是声明具名函数的语法:
def greet {|name| [str "hello " $name]}
声明不会被提升:`def` 必须在使用该名字之前执行。
[greet "Alice"] # error — greet is not defined yet def greet {|name| ...}
在顶层,`set` 写入的是同一个模块命名空间,因此这样也能工作——但模块声明的惯例是使用 `def`:
set max-runs 3 # works at top level — writes the namespace def retry-limit 3 # preferred for module declarations
## 局部作用域 —— `set`
在函数内部,`set` 会重新绑定已有的局部变量,或在首次使用时创建一个新的局部变量。循环计数器、累加器和临时空间都应使用这种形式:
def total {|prices| set sum 0 for price in $prices { set sum ($sum + $price) } $sum }
局部变量按引用捕获,因此闭包中 `set` 外层局部变量会修改原变量:
def make-counter {|| set n 0 {|| set n ($n + 1) $n} } def bump [make-counter] [bump] # => 1 [bump] # => 2
对于单位递增,优先使用 `incr` 而不是 `set n ($n + 1)`(见语法与风格)。
## 函数内的 `def` 写入模块
函数内的值 `def` 不会创建局部变量。它会写入模块命名空间,与出现在顶层时完全一样:
def ready false def init {|| def ready true # updates the module binding } [init] $ready # => true
回调正是这样更新共享模块状态的——在模块作用域声明槽位,然后在回调内用 `def` 绑定它:
def last-event nil def on-event {|ev| def last-event $ev } def start {|| [pub/subscribe "events" "on-event"] } [start] # ... when an event arrives, the module binding updates
在同一位置使用 `set` 会创建一个闭包局部变量,而模块绑定保持不变:
def status "idle" def run {|| set status "busy" # new local — module $status stays "idle" $status }
如果模块值是集合,`<<` 可以从任意闭包修改这个共享绑定(裸形式,无需 `set`):
def seen @{} def watch {|| @{ 1 2 3 } |> each { $seen << $it } }
## 如何选择
def config %{ :host "localhost" :port 8080 } # module state def handler {|req res| ... } # named function def process {|items| set count 0 # local counter for item in $items { incr count } $count }