BLOGE
0.9.8-RC1在线版 · 事实校验 2026-09-15 · English
附录 F —— 决策表
本附录是 decision_table 节点类型的权威速查手册。此处列出的每条语法规则、命中策略保证、输出形状和错误码均直接来源于 bloge 源码与合规测试装置。
当你需要查询精确语义时,请直接使用本附录,无需重读第 5 章。若想从概念引导出发,请从 第 5 章——做决策的分支开始。若需动手练习,请参见 第 34 章 Lab 6。
语法速查
decision_table <id>(
<param1> = <expr1>,
<param2> = <expr2>,
...
) [hit=<first|unique|any|collect>] [-> <OutputType>] {
rule (<param>: <predicate>, ...) -> <output>
...
[otherwise -> <output>]
}
hit=默认为first。-> <OutputType>可省略;省略时编译器从第一条规则的输出推断类型。- 每条
rule在(...)中声明要测试的参数。 otherwise仅在所有显式rule都不匹配时才执行(或对collect策略而言,总是追加为一个条目——见§otherwise 语义)。
最小示例 —— hit=first + 标量输出:
graph creditScreening {
decision_table credit_tier(score = applicant.output.score) hit=first -> String {
rule (score: score >= 750) -> "platinum"
rule (score: 680 <= score < 750) -> "gold"
otherwise -> "rejected"
}
}
命中策略矩阵
| 策略 | 多条规则命中 | 无匹配且无 otherwise | 抛出异常 |
|---|---|---|---|
first | 取声明顺序的第一个匹配 | RUNTIME_DECISION_TABLE_NO_MATCH | CODE_NO_MATCH |
unique | 抛出 RUNTIME_DECISION_TABLE_AMBIGUOUS_MATCH | RUNTIME_DECISION_TABLE_NO_MATCH | CODE_AMBIGUOUS_MATCH / CODE_NO_MATCH |
any | 若所有匹配输出相等(Objects.equals)则允许;否则抛出 RUNTIME_DECISION_TABLE_CONFLICTING_MATCH | RUNTIME_DECISION_TABLE_NO_MATCH | CODE_CONFLICTING_MATCH / CODE_NO_MATCH |
collect | 收集所有匹配到 output.items | 返回 { items: [] },不抛异常 | — |
first 示例 —— 第一条命中的规则获胜:
graph decisionBasicFirst {
decision_table credit_tier(score = applicant.output.score) hit=first -> String {
rule (score: score >= 750) -> "platinum"
rule (score: 680 <= score < 750) -> "gold"
otherwise -> "rejected"
}
}
unique 示例 —— 至多一条规则命中,否则报错:
graph decisionBasicUnique {
decision_table credit_band(score = applicant.output.score) hit=unique -> String {
rule (score: score >= 750) -> "prime"
rule (score: 650 <= score < 750) -> "standard"
otherwise -> "manual"
}
}
any 示例 —— 多条规则可以同时命中,前提是输出值相同:
graph decisionBasicAny {
decision_table approval(score = ctx.score) hit=any -> String {
rule (score: score >= 700) -> "approved"
rule (score: score >= 750) -> "approved"
otherwise -> "manual"
}
}
collect 示例 —— 所有命中规则都贡献到 output.items:
graph decisionBasicCollect {
decision_table applicable_discounts(score = applicant.output.score) hit=collect -> String {
rule (score: score >= 700) -> "loyalty"
rule (score: score >= 760) -> "premium"
}
}
otherwise 语义
| 策略 | 存在 otherwise | 不存在 otherwise |
|---|---|---|
first | 无显式规则命中时触发 | 无匹配 → RUNTIME_DECISION_TABLE_NO_MATCH |
unique | 无显式规则命中时触发 | 无匹配 → RUNTIME_DECISION_TABLE_NO_MATCH |
any | 无显式规则命中时触发 | 无匹配 → RUNTIME_DECISION_TABLE_NO_MATCH |
collect | 无论显式规则是否命中,总是追加一个条目 | 空匹配 → 返回 { items: [] },不抛异常 |
collect的特殊之处:对于first、unique、any,otherwise是"兜底"——只在其他规则都不匹配时才触发。对于collect,它是一条无条件规则,总是把自己的输出追加到items中。这一差异由decision-table/collect-otherwiselint 规则(INFO 严重度)标记,提示你确认这是否是有意为之。
输出形状参考
共三种形状。同一张表中的所有 rule(包括 otherwise)必须使用相同的形状——混用形状会导致编译错误(COMPILER_DECISION_TABLE_OUTPUT_SHAPE_MISMATCH)。
| 形状 | 声明语法 | 下游访问方式 |
|---|---|---|
| 标量(scalar) | -> "value" / -> 3.5 | <node>.output.value |
| 命名字段(named fields) | -> { rate: 3.5, maxTerm: 30 } | <node>.output.rate、<node>.output.maxTerm |
| 收集条目(collect items) | hit=collect + 任意输出 | <node>.output.items(List 类型) |
多输出(命名字段)示例:
graph decisionMultiOutput {
decision_table loan_terms(
score = applicant.output.score,
amount = loan.output.amount
) hit=unique -> { rate: Decimal, maxTerm: Int } {
rule (score: score >= 750, amount: amount <= 500000) -> { rate: 3.5, maxTerm: 30 }
otherwise -> { rate: 6.0, maxTerm: 5 }
}
}
in 运算符
in 在规则谓词中测试集合成员关系,共两种形式。
形式 A —— 字面量数组
rule (type: type in ["vip", "enterprise", "gov"]) -> "priority"
- 右侧是字面量数组:
[lit1, lit2, ...]。 - 每个元素必须是 String、Number 或 Boolean 字面量——不允许表达式、变量或节点路径。
- 违反此规则会触发编译错误:
COMPILER_DECISION_TABLE_IN_LITERAL_ONLY。 - 空数组(
type in [])永远不匹配任何输入。
示例:
graph decisionTableInStatic {
decision_table customer_tier(type = ctx.type) hit=first -> String {
rule (type: type in ["vip", "enterprise", "gov"]) -> "priority"
otherwise -> "standard"
}
}
形式 B —— 动态参数引用
rule (type: type in allowed) -> "priority"
- 右侧是单个已声明参数名,在运行时必须解析为
Collection类型。 - 不允许深路径如
ctx.allowed、obj.list、a[0]——请通过decision_table(...)头部的参数绑定把集合传入。 - 若参数在运行时解析为非
Collection类型(如String或Integer),bloge 抛出带有RUNTIME_DECISION_TABLE_INVALID_COLLECTION_PARAM代码的DecisionTableViolationException。异常消息包含in表达式的行列号和实际运行时类型。 - 左值为
null时直接短路返回 false(不抛异常)。
示例:
graph decisionTableInDynamic {
decision_table customer_tier(type = ctx.type, allowed = ctx.allowed) hit=first -> String {
rule (type: type in allowed) -> "priority"
otherwise -> "standard"
}
}
等价语义 —— 两种形式一致
形式 A 和形式 B 内部均使用数值等价比较(evaluateBinaryOp(EQ_EQ))。这意味着 Integer(700) 与 BigDecimal(700.0) 被视为相等。你不会因为不同图节点产生的数值类型不同而遭遇无声的不匹配。
常见陷阱
1. 作用域越界 —— 在规则中引用非参数路径
// ❌ 编译错误:COMPILER_DECISION_TABLE_RULE_SCOPE_VIOLATION
rule (score: applicant.output.score >= 750) -> "prime"
// ✅ 先通过参数绑定传入值
decision_table credit_band(score = applicant.output.score) hit=unique -> String {
rule (score: score >= 750) -> "prime"
...
}
2. 输出形状不一致
// ❌ 编译错误:COMPILER_DECISION_TABLE_OUTPUT_SHAPE_MISMATCH
decision_table tier(score = ctx.score) hit=first {
rule (score: score >= 750) -> "prime" // 标量
otherwise -> { label: "manual", flag: true } // 命名字段——形状不一致!
}
3. in 右侧不是简单参数名
// ❌ 编译错误:COMPILER_DECISION_TABLE_IN_INVALID_RIGHT
rule (type: type in ctx.allowed) -> "priority" // 深路径不允许
// ✅ 在参数列表中绑定
decision_table tier(type = ctx.type, allowed = ctx.allowed) hit=first -> String {
rule (type: type in allowed) -> "priority"
...
}
4. in 右侧参数在运行时不是 Collection
若绑定给 in 右侧的参数在运行时解析为非 Collection 值(如 String 或 Integer),bloge 抛出:
RUNTIME_DECISION_TABLE_INVALID_COLLECTION_PARAM
异常消息会指出具体行列号和实际运行时类型。请修复产生错误类型的上游节点或绑定表达式。
5. collect + otherwise —— 总是追加,而不只是兜底
// [INFO] decision-table/collect-otherwise
// 这里的 otherwise 子句会在每次运行时都将 "baseline" 追加到 items,
// 即使其他规则也命中了。请确认这是否是有意为之。
decision_table discounts(score = ctx.score) hit=collect -> String {
rule (score: score >= 700) -> "loyalty"
otherwise -> "baseline" // 总是被追加
}
运行时错误码
所有运行时违规都以 DecisionTableViolationException 抛出。code 字段携带以下冻结的 wire 字符串之一:
| Wire 字符串 | 触发时机 |
|---|---|
RUNTIME_DECISION_TABLE_NO_MATCH | first、unique 或 any——没有规则命中且未声明 otherwise |
RUNTIME_DECISION_TABLE_AMBIGUOUS_MATCH | unique——超过一条规则命中 |
RUNTIME_DECISION_TABLE_CONFLICTING_MATCH | any——多条规则命中但输出值不相等 |
RUNTIME_DECISION_TABLE_INVALID_COLLECTION_PARAM | in <param>——参数未解析为 Collection |
这些字符串是冻结契约。在应用程序错误处理器或测试断言中匹配时,请原文复制。
编译期错误码
bloge 编译器在编译期拒绝无效的决策表定义。主要错误标识符(用于诊断信息和 CI lint 输出):
| 标识符 | 触发原因 |
|---|---|
COMPILER_DECISION_TABLE_IN_LITERAL_ONLY | in [...] 数组包含非字面量元素 |
COMPILER_DECISION_TABLE_IN_INVALID_RIGHT | in <expr> 右侧不是单个参数名 |
COMPILER_DECISION_TABLE_RULE_SCOPE_VIOLATION | 规则谓词引用了已声明参数之外的内容 |
COMPILER_DECISION_TABLE_OUTPUT_SHAPE_MISMATCH | 规则使用了不一致的输出形状(标量 vs. 命名字段) |
COMPILER_DECISION_TABLE_OUTPUT_TYPE_INVALID | 命名输出类型注解中的字段不符合必需的 name: Type 形式 |
COMPILER_DECISION_TABLE_UNKNOWN_PARAMETER | 规则条件引用了表头中未声明的参数 |
COMPILER_DECISION_TABLE_DUPLICATE_CONDITION | 两条规则的条件完全相同 |
COMPILER_DECISION_TABLE_PARAM_RESERVED | 参数名与 DSL 关键字冲突 |
COMPILER_DECISION_TABLE_PARAM_DUPLICATE | 表头中存在重复参数名 |
COMPILER_DECISION_TABLE_PARAM_NODE_CONFLICT | 参数名与图节点名冲突 |
最小可运行示例
示例 1 —— hit=first + otherwise
graph ex1First {
decision_table credit_tier(score = applicant.output.score) hit=first -> String {
rule (score: score >= 750) -> "platinum"
rule (score: 680 <= score < 750) -> "gold"
otherwise -> "rejected"
}
}
预期:score=720 → credit_tier.output.value == "gold"。
示例 2 —— hit=unique —— 歧义匹配错误路径
graph ex2Unique {
decision_table risk_band(score = ctx.score) hit=unique -> String {
rule (score: score >= 650) -> "eligible"
rule (score: score >= 700) -> "preferred"
// 无 otherwise —— 若 score >= 700 则两条规则都命中 → RUNTIME_DECISION_TABLE_AMBIGUOUS_MATCH
}
}
预期:score=720 → 抛出 DecisionTableViolationException,code 为 RUNTIME_DECISION_TABLE_AMBIGUOUS_MATCH。