结构与宏
有两种形式可以扩展语言本身:defstruct 声明记录类型,defmacro 定义一种命令,
它的参数以“被引用的表单”传入。
(defstruct Name field...) 至少需要一个字段,并生成三类命令:
| 名字 | 含义 |
|---|---|
Name |
构造器:按定义顺序每个字段一个参数 |
Name.field |
访问器:从该结构体的值里读取一个字段 |
Name? |
判定:值为该结构体时为真 |
(defstruct Point x y)(let point (Point #3 #4))(echo (Point.x $point) (Point.y $point))(if (Point? $point) (echo a point))结构体是普通值:可以放进列表和映射、传给函数,也可以被字符串插值捕获。
它按字段顺序显示,例如 (Point #3 #4);字段是文本时保留引号,显示为 (Point "3" "4")。
它按名字与字段比较,并遵循通常的按值拷贝语义。
结构体不可变,修改字段意味着重新构造一个值。
访问器从值本身读取字段,而不是查当前定义,因此重定义结构体不会让已有的值失效:
(defstruct Point x y)(let old (Point #1 #2))(defstruct Point x)(Point.y $old)type 可以查看这些成员,不会调用它们:
(defstruct Point x y)(type Point)(type Point.x)(type -t Point)名字不能与内置形式、函数或宏冲突;构造器、访问器和判定名都属于该结构体。
(defmacro Name (parameters...) body...) 定义一种命令,它的参数不会被求值:
每个参数绑定到对应实参的被引用表单,宏体执行后返回一个表单,再由 Shell 在调用方的作用域里求值。
反引号开始一个模板,, 把求值后的表达式插入模板,,@ 把一个列表表单摊平到外层调用里:
(defmacro unless (condition body) `(if (not ,$condition) ,$body))
(unless (test -f config.txt) (echo "configuration missing"))宏参数和普通变量一样用 $name 读取。由于返回的表单在调用点求值,
模板中出现的变量指向调用方的绑定:
(defmacro greet (who) `(echo hello ,$who))(let who parsh)(greet $who)quote 构造一个不求值的表单。表单是普通值:eq 按语法比较,交互提示符按源码显示。
(quote (a b))(eq (quote (a b)) (quote (a b)))(defmacro begin (body) `(do ,@$body))(begin ((echo one) (echo two)))unquote 接受被引用的表单或标量值;splicing 接受被引用的列表表单,或者由被引用表单组成的列表。
可选与剩余宏参数
Section titled “可选与剩余宏参数”(defmacro when (condition &rest body) `(if ,$condition (do ,@$body)))(when #true (str first) (str last))宏的显式实参保持为 Form,不会提前求值。&rest 收集 Form 列表,&optional 的缺省表达式
在宏执行时求值,可引用此前参数;裸可选参数缺省为 nil。宏体中的 return 提前返回展开表单,
展开代码中的 return、break、continue 则作用于调用方的函数或循环。
- 宏参数支持与函数相同的
&optional和&rest;实参保留为未求值的 Form。 - 宏和结构体是全局定义,即使写在函数体内也一样。
- 没有卫生性(hygiene):模板会把表单原样插入调用方作用域。
- 宏和结构体成员不能作为管道阶段。
- 嵌套的 quasiquote 会原样拷贝,不会按层级展开。
- 结构体没有字段修改器,也不支持动态字段名。
标量字面值通过 quote、quasiquote 和 unquote 时保留类型。表单源码中的标量带 #,数值拼写会规范化,例如 #001 显示为 #1;字面值节点按类型和值比较,忽略源码位置。