145 lines
6.4 KiB
Markdown
145 lines
6.4 KiB
Markdown
# 核心语法
|
|
|
|
每个片段都链接到随 skill 提供、经过检查的完整源码。先看它所属的角色和调用链,再复用到项目;不要把片段单独当作完整 HTTP 项目。
|
|
|
|
## 类型和函数
|
|
|
|
源码:[模板 App](../assets/template/module/hello/greeting/app.dever)
|
|
|
|
```dever
|
|
greet(name: Text) (message: Text) {
|
|
message = "Hello, " + name
|
|
}
|
|
```
|
|
|
|
函数写成 `name(输入) (命名输出) { 语句 }`,不写 function/return,调用按位置传参。没有输入或输出时也要保留 `()`。一个输出直接得到值;多个输出按名字访问。
|
|
|
|
App 的参数、返回值和向外传播的错误都属于公开接口,不能暴露 Domain/Adapter 私有类型。
|
|
|
|
函数命名表达业务行为,调用路径不附加主题文件名。相关函数、类型和同名分句可以同处一个内聚文件;拆分与复用的判断见 [文件与函数组织](development.md#文件与函数组织),不按函数长度自动增加 helper。
|
|
|
|
常用类型:`Bool`、`Int`(检查溢出)、`Float`、精确 `Decimal`、`Text`、`Bytes`、`Uuid`、`Id`、`DateTime`、`Date`、`Time`、`Duration`、`Secret`、`Json`。可空用 `T?`,集合用 `List<T>`、`Map<K,V>`。Model ID 是每个 Model 独有的名义类型,不等于任意 Int。不得把不同表的 ID 混用。
|
|
|
|
record 用字段初始化;choice 用分支构造。以下 State、label、display 和 grade 位于同一份语法测试,测试入口覆盖每个分句;实际业务无需创建这些演示函数。
|
|
|
|
源码:[分句测试](../assets/examples/collections/test/content/article/clauses.dever)
|
|
|
|
```dever
|
|
type State {
|
|
Draft
|
|
Published
|
|
Rejected(reason: Text)
|
|
}
|
|
```
|
|
|
|
源码:[分句测试](../assets/examples/collections/test/content/article/clauses.dever)
|
|
|
|
```dever
|
|
label(state: State.Published) (text: Text) {
|
|
text = "published"
|
|
}
|
|
|
|
label(state: State.Rejected(reason)) (text: Text) {
|
|
text = reason
|
|
}
|
|
|
|
label(state: other) (text: Text) {
|
|
text = "draft"
|
|
}
|
|
```
|
|
|
|
## 分支与边界
|
|
|
|
用同名函数的不同分句表达条件,不写 if/else/switch/match、条件表达式或递归。这些分句要连续,参数数量和输出声明一致。
|
|
|
|
每种输入必须恰好匹配一个分句:不能漏掉,也不能重叠;写在前面不会优先匹配。`other` 表示该参数位置没有被其他明确模式覆盖的值。
|
|
|
|
源码:[分句测试](../assets/examples/collections/test/content/article/clauses.dever)
|
|
|
|
```dever
|
|
display(value: Text) (text: Text) {
|
|
text = value
|
|
}
|
|
|
|
display(value: null) (text: Text) {
|
|
text = "missing"
|
|
}
|
|
|
|
grade(score: Int < 60) (text: Text) {
|
|
text = "fail"
|
|
}
|
|
|
|
grade(score: Int >= 60) (text: Text) {
|
|
text = "pass"
|
|
}
|
|
```
|
|
|
|
支持完整类型、choice 分支、null、Bool/Text/数值字面量、Int/Decimal 范围与 other 模式;多输入检查全部输入组合。任一函数都必须为命名输出赋值。不要写 `return` 提前退出,按分句表示结果与失败。
|
|
|
|
字面量分句适合明确的状态和规则,不意味着可以按某个用户 ID、租户名字或样例数据写业务特例。运行时配置与可信身份的归属见 [配置与硬编码](development.md#配置与硬编码)。
|
|
|
|
## 集合与处理函数
|
|
|
|
Dever 用 `each` 同时表达映射和遍历,没有独立的 `map`:处理函数有一个输出时生成 List,没有输出时逐项执行动作。`filter` 按 Bool 输出筛选,`reduce` 按初始值逐项累积。它们是语言核心操作,不需要导入集合库。
|
|
|
|
下面的 App 选出已发布且标题非空的文章,整理标题并统计总字数。输入、结果类型与完整调用链都在 [集合项目](../assets/examples/collections/) 内。
|
|
|
|
源码:[文章 App](../assets/examples/collections/module/content/article/app.dever)
|
|
|
|
```dever
|
|
type Article {
|
|
title: Text
|
|
published: Bool
|
|
}
|
|
|
|
type Summary {
|
|
titles: List<Text>
|
|
title_length: Int
|
|
}
|
|
|
|
summarize(articles: List<Article>) (summary: Summary) {
|
|
published = filter(domain.is_published, articles)
|
|
titles = each(domain.published_title, published)
|
|
summary = Summary {
|
|
titles = titles
|
|
title_length = reduce(domain.add_title_length, titles, 0)
|
|
}
|
|
}
|
|
```
|
|
|
|
处理函数直接使用有业务含义的命名函数。参数顺序、类型和输出由编译器检查,不使用 lambda 或运行期闭包:
|
|
|
|
源码:[文章 Domain](../assets/examples/collections/module/content/article/domain.dever)
|
|
|
|
```dever
|
|
is_published(article: app.Article) (selected: Bool) pure {
|
|
selected = article.published and text.trim(article.title) != ""
|
|
}
|
|
|
|
published_title(article: app.Article) (title: Text) pure {
|
|
title = text.trim(article.title)
|
|
}
|
|
|
|
add_title_length(title: Text, total: Int) (next: Int) pure {
|
|
next = total + length(title)
|
|
}
|
|
```
|
|
|
|
`filter` 的处理函数接收一个元素,输出 Bool;`each` 的单输出成为新 List 的元素;`reduce` 按“元素、当前累计值”传参,输出下一个累计值。顺序保持不变,空集合的 reduce 返回初始值。本例初始值为 0。
|
|
|
|
集合项目的 [summary 测试](../assets/examples/collections/test/content/article/summary.dever) 用 `each(assert_clean_title, actual.titles)` 逐条断言。这个处理函数没有输出,所以该调用没有返回 List。测试还验证筛选顺序、清理结果、总字数和空集合。
|
|
|
|
## 失败和能力
|
|
|
|
业务错误通过 choice 的 `error` 分支声明,`fail` 传播,`result(call)` 显式捕获;普通调用直接得到成功值。不能用字符串/任意 JSON 代替类型化失败,不能捕获后悄悄吞掉错误。错误映射到 HTTP 的方式见 application.md。
|
|
|
|
Domain 的纯计算函数可以声明 `pure`,它调用的方法也要满足纯计算要求。Model 的 REST 字段转换只能调用合规的同领域 pure Domain。文件、网络等外部操作经 Port/Adapter,不直接放进普通 App 或 Domain。
|
|
|
|
不需要写 async/await,普通调用依次完成。要并发执行时用 `run` 启动子任务,再用 `wait/stop` 收尾;不能把任务留到当前作用域之外。
|
|
|
|
Group/Channel/Stream 的使用范围由编译器检查,不能跨等待、线程或请求随意保存句柄。`result` 只负责捕获结果,不改变调度,也不让原本禁止的操作变成合法。
|
|
|
|
`Secret` 不可打印、比较或转为普通 Text。密码使用现有 crypto 能力,不自制哈希。类型、分句或调用能力报错时修正真实问题,不增加中转函数、通用包装或吞错分句来绕过检查。
|
|
|
|
更多完整语法以官方核心仓库同版本 `LANGUAGE.md` 为准;新建项目先使用本 skill 的已验证模板。
|