sml

SML { ❄ }

第 4 章:include 与命名空间

第 4 章:include 与命名空间

当配置越来越长,你会想把不同部分拆到不同文件。SML 用 include 把多个文件拼成一个逻辑整体。

设计哲学:从极简到丰富,功能可裁剪。基础 include 默认开启;复杂的多目标 / 通配 / 正则需 @feature enable 显式开启,避免重蹈 YAML 过度复杂。

4.1 基础内联:include "文件"

# app.sml
include "common.sml"
app: myapp

common.sml 的内容会被原样贴进来,就像你手写在那一样。也可以出现在块内部(局部注入字段):

database {
    include "conf.d/db.sml"   # 只给 database 块注入字段
    pool: 16
}

要点:

4.2 命名空间:把文件装进独立作用域

如果 common.sml 里有很多键,直接内联可能和主文件撞名。用 as 给它一个命名空间:

include "ui.sml" as ui
# 现在 ui.sml 里的键都被收在 ui 下面
title: ui.title

as ui 等价于把 ui.sml 的内容包进 ui { ... } 块。

不带扩展名 = 默认命名空间

干净规则:带扩展名 = 内联;不带扩展名 = 命名空间(名字取文件名)。

include "ui"          # 等价于 include "ui.sml" as ui
include "ui.sml"      # 内联(带扩展名)
include "ui.sml" as ui.form.widgets   # 显式指定,优先

4.3 点分路径(嵌套命名空间)

as 后面支持 a.b.c,等价于 Rust 的模块路径:

include "widgets.sml" as ui.form.widgets

展开后逻辑结构是 ui { form { widgets { ... } } }。用 ui.form.widgets.Button 这样的前缀去访问。

4.4 宏与契约也随命名空间隔离

命名空间不止隔离数据,还隔离片段与契约定义。在被包含文件里定义的 @contract / @name,对外必须用 ns. 前缀引用:

# widgets.sml 内部
@contract Button { label: str }
@name primary = { label: "OK" }

# 主文件引用时必须带前缀
@is ui.form.widgets.Button
button: &ui.form.widgets.primary

文件内部对自身宏的自引用仍按本地名解析(不用前缀),只有对外暴露才需要 ns. 前缀。解析器按"命名空间栈"自动加前缀。

4.5 多目标与 import 别名

逗号分隔一次包含多个目标;importinclude 的等价写法:

include "a.sml", "b.sml" as y, "c"
import ui.buttons, admin.panel

多目标 / import 别名属于"丰富层",需 @feature enable multi 开启(见 4.7)。

4.6 冲突即报错(不静默)

命名空间是独占作用域,绝不静默覆盖:

4.7 Feature 分层(可裁剪)

feature 能力 默认
0 include 基础 include "x.sml" 内联
1 namespace as ns + 点分路径 + 宏/契约隔离
1 implicit-ns 无扩展名 include "foo"as foo
2 multi 逗号多目标、import 别名
2 glob * 通配 dir/*.sml
3 regex re: / /.../ 正则匹配
3 ext-rewrite -> .sml 把非 sml 文件当 sml 解析

开启方式:

@feature enable glob
include "widgets/*.sml"

4.8 零拷贝(性能小贴士)

include 不会把文件内容深拷贝拼成大字符串。每个文件读入后只持有其切片,解析器消费一段"切片流",遇到 as a.b 时插入零拷贝的开块 / 闭块字面量。文件内容只解析一次,内存 = 各文件切片之和。你无需关心,但知道它很高效就好。

4.9 动手试一试

  1. common.sml,写 @net { region: cn-north-1 timeout: 30 }
  2. app.sml
    include "common.sml" as cfg
    service {
        region: cfg.net.region
        name: gateway
    }
    
  3. 解析 app.sml,确认 service.region 取到 cn-north-1

第 5 章:契约系统

动手练习

读完本章,在下面的编辑器里直接修改 SML 并点“运行”,立刻看到解析结果或校验错误——有输出才能高效学习。

✍ 动手练习 include "ui" as ui,其中 ui.sml 有 title 与 powered。主文件通过 ui.title / ui.powered 访问。
💡 提示:增加一个 second.sml 文件并在主文件里 include "second" as s,访问 s.xxx。
虚拟文件(可在 include 中引用)
ui.sml
title: "My App"
powered: Snoware
✍ 自测考题:第 4 章自测:include 与命名空间 得分 0 / 4
Q1. include "ui" as ui 的作用是什么?
Q2. 主文件写 title: ui.title,ui 来自哪里?
Q3. 判断:include 可以引入多个文件,每个都能用 as 起不同命名空间。
Q4. glob 写法 include "modules/*.sml" as m 会?