迭代(Iteration)
Rego 中的迭代与其他语言中的 for 循环有根本性的不同。Rego 没有显式的循环结构,而是通过枚举可能的绑定关系来实现迭代。只要集合中至少有一条路径能让所有条件同时成立,规则就会成功。
基础迭代:some item in collection
最常见的迭代语法是用 some ... in ... 引入一个局部变量,让它依次绑定集合中的每个成员:
rego
import rego.v1
products := {"a-phone", "b-phone", "a-pad"}
# 成功:集合中存在以 "-phone" 结尾的成员
has_phone if {
some item in products
endswith(item, "-phone")
}
# 失败:集合中不存在以 "-car" 结尾的成员
has_car if {
some item in products
endswith(item, "-car")
}理解迭代语义:Rego 会对集合的每个成员尝试执行后续条件,只要存在任意一个成员能让所有条件同时成立,规则就成功。这是"存在量化"语义——等价于"集合中至少有一个成员满足条件"。
数组的迭代语法完全相同:
rego
import rego.v1
array := ["a-phone", "b-phone", "a-pad"]
has_phone if {
some item in array
endswith(item, "-phone")
}键值对迭代
对数组和对象,还可以用 some key, value in collection 同时获取键(索引)和值:
rego
import rego.v1
array := ["a-phone", "b-phone", "a-pad"]
# 索引 > 0 且值以 "-phone" 结尾
has_phone_after_index_0 if {
some index, item in array # 枚举 0:"a-phone", 1:"b-phone", 2:"a-pad"
endswith(item, "-phone")
index > 0 # 1:"b-phone" 同时满足两个条件 → 规则成功
}对象的键值对迭代:
rego
import rego.v1
catalog := {
"x-1": "a-phone",
"x-2": "b-phone",
"y-1": "a-pad"
}
# 键以 "x-" 开头且值以 "-phone" 结尾
has_x_key_phone if {
some key, value in catalog
startswith(key, "x-")
endswith(value, "-phone")
}_ 匿名变量
当只关心键或值之一时,用 _ 作为占位符忽略不需要的部分:
rego
import rego.v1
catalog := {
"x-1": "a-phone",
"x-2": "b-phone",
"y-1": "a-pad"
}
# 只关心键,不关心值
has_x_key if {
some key, _ in catalog
startswith(key, "x-")
}每个 _ 都是独立的匿名变量,可以在同一条规则中多次使用。
嵌套迭代
在同一条规则中使用多个 some ... in 可以实现嵌套迭代:
rego
import rego.v1
catalog1 := {"a-phone", "b-phone", "a-pad"}
catalog2 := {"a-watch", "b-watch", "a-phone"}
# 两个集合中存在相同的值
have_common_item if {
some v1 in catalog1
some v2 in catalog2
v1 == v2 # "a-phone" 在两个集合中都存在 → 成功
}对于更深层的嵌套结构,可以逐层引入迭代变量:
rego
import rego.v1
catalog := {
"x-1": {"name": "a-phone", "suppliers": ["a-corp", "z-corp"]},
"x-2": {"name": "b-phone", "suppliers": ["b-corp", "z-corp"]},
"y-1": {"name": "a-pad", "suppliers": ["a-corp"]}
}
# catalog 中某个产品的 suppliers 包含 "b-corp"
has_supplier_b_corp if {
some item in catalog
some supplier in item.suppliers
supplier == "b-corp"
}自由迭代与 _
引入中间变量后,也可以用更简洁的"自由迭代"形式:
rego
import rego.v1
# 等价于上面的 has_supplier_b_corp
# 用 some 声明迭代变量,直接在路径中使用
has_supplier_b_corp_free if {
some id, index
catalog[id].suppliers[index] == "b-corp"
}
# 最简形式:用 _ 替换所有不关心的迭代维度
has_supplier_b_corp_wildcard if {
catalog[_].suppliers[_] == "b-corp"
}⚠️ 注意:不带
some声明也不带_的自由迭代(直接写catalog[id].suppliers[index])是早期 Rego v0 的写法,容易引发难以察觉的 bug,我们将在"常见陷阱"章节详细讨论。现代 Rego v1 中应始终使用some声明或_占位符。
动手实验
下面的 Killercoda 环境已预装 OPA,你可以在真实的终端中运行上述所有例子:
