谓词¶
谓词用于描述构成 QL 程序的逻辑关系。
严格地说,谓词评估为一组元组。例如,考虑以下两个谓词定义
predicate isCountry(string country) {
country = "Germany"
or
country = "Belgium"
or
country = "France"
}
predicate hasCapital(string country, string capital) {
country = "Belgium" and capital = "Brussels"
or
country = "Germany" and capital = "Berlin"
or
country = "France" and capital = "Paris"
}
谓词 isCountry
是单元组的集合 {("Belgium"),("Germany"),("France")}
,而 hasCapital
是二元组的集合 {("Belgium","Brussels"),("Germany","Berlin"),("France","Paris")}
。这些谓词的元数分别为 1 和 2。
通常,谓词中的所有元组都具有相同数量的元素。谓词的元数是元素的数量,不包括可能的 result
变量。有关更多信息,请参见“带结果的谓词”。
QL 中有许多内置谓词。您可以在任何查询中使用它们,而无需导入任何其他模块。除了这些内置谓词之外,您还可以定义自己的
定义谓词¶
定义谓词时,您应该指定
- 关键字
predicate
(对于无结果的谓词),或结果的类型(对于带结果的谓词)。 - 谓词的名称。这是一个以小写字母开头的标识符。
- 谓词的参数(如果有),用逗号分隔。对于每个参数,指定参数类型和参数变量的标识符。
- 谓词主体本身。这是一个用大括号括起来的逻辑公式。
无结果的谓词¶
这些谓词定义以关键字 predicate
开头。如果值满足主体中的逻辑属性,则谓词对于该值成立。
例如
predicate isSmall(int i) {
i in [1 .. 9]
}
如果 i
是一个整数,则 isSmall(i)
成立,当且仅当 i
是一个小于 10 的正整数。
带结果的谓词¶
您可以通过用结果类型替换关键字 predicate
来定义带结果的谓词。这引入了特殊变量 result
。
例如
int getSuccessor(int i) {
result = i + 1 and
i in [1 .. 9]
}
如果 i
是一个小于 10 的正整数,则谓词的结果是 i
的后继。
请注意,您可以像对谓词的任何其他参数一样使用 result
。您可以以您喜欢的任何方式表达 result
与其他变量之间的关系。例如,给定一个谓词 getAParentOf(Person x)
,它返回 x
的父母,您可以定义一个“反向”谓词,如下所示
Person getAChildOf(Person p) {
p = getAParentOf(result)
}
谓词也可能为其参数的每个值返回多个结果(或根本不返回)。例如
string getANeighbor(string country) {
country = "France" and result = "Belgium"
or
country = "France" and result = "Germany"
or
country = "Germany" and result = "Austria"
or
country = "Germany" and result = "Belgium"
}
- 在这种情况下
- 谓词调用
getANeighbor("Germany")
返回两个结果:"Austria"
和"Belgium"
。 - 谓词调用
getANeighbor("Belgium")
不返回任何结果,因为getANeighbor
没有为"Belgium"
定义result
。
- 谓词调用
递归谓词¶
QL 中的谓词可以是递归的。这意味着它直接或间接地依赖于自身。
例如,您可以使用递归来改进上面的示例。就目前而言,在 getANeighbor
中定义的关系不是对称的——它没有捕捉到如果 x 是 y 的邻居,则 y 是 x 的邻居这一事实。捕捉到这一点的一种简单方法是递归调用此谓词,如下所示
string getANeighbor(string country) {
country = "France" and result = "Belgium"
or
country = "France" and result = "Germany"
or
country = "Germany" and result = "Austria"
or
country = "Germany" and result = "Belgium"
or
country = getANeighbor(result)
}
现在 getANeighbor("Belgium")
也返回结果,即 "France"
和 "Germany"
。
有关递归谓词和查询的更一般性讨论,请参见“递归”。
谓词的种类¶
谓词有三种,分别是:非成员谓词、成员谓词和特征谓词。
非成员谓词是在类之外定义的,也就是说,它们不是任何类的成员。
有关其他谓词种类的更多信息,请参见“类”主题中的特征谓词和成员谓词。
以下是一个示例,展示了每种类型的谓词
int getSuccessor(int i) { // 1. Non-member predicate
result = i + 1 and
i in [1 .. 9]
}
class FavoriteNumbers extends int {
FavoriteNumbers() { // 2. Characteristic predicate
this = 1 or
this = 4 or
this = 9
}
string getName() { // 3. Member predicate for the class `FavoriteNumbers`
this = 1 and result = "one"
or
this = 4 and result = "four"
or
this = 9 and result = "nine"
}
}
您也可以为这些谓词中的每一个添加注释。请参阅注释列表,了解每种类型的谓词可用的注释。
绑定行为¶
必须能够在有限的时间内评估谓词,因此它所描述的集合通常不允许是无限的。换句话说,谓词只能包含有限数量的元组。
当 QL 编译器可以证明谓词包含未限制为有限数量值的变量时,它会报告错误。有关更多信息,请参见“绑定”。
以下是一些无限谓词的示例
/*
Compilation errors:
ERROR: "i" is not bound to a value.
ERROR: "result" is not bound to a value.
ERROR: expression "i * 4" is not bound to a value.
*/
int multiplyBy4(int i) {
result = i * 4
}
/*
Compilation errors:
ERROR: "str" is not bound to a value.
ERROR: expression "str.length()" is not bound to a value.
*/
predicate shortString(string str) {
str.length() < 10
}
在 multiplyBy4
中,参数 i
被声明为一个 int
,它是一个无限类型。它在二元运算符 *
中使用,而该运算符不会绑定其操作数。 result
最初是未绑定的,并且仍然是未绑定的,因为它与 i * 4
进行相等性检查,而 i * 4
也是未绑定的。
在 shortString
中,str
仍然是未绑定的,因为它使用无限类型 string
声明,并且内置函数 length()
不会绑定它。
绑定集¶
有时您可能想要定义一个“无限谓词”,因为您只打算在受限的参数集上使用它。在这种情况下,您可以使用 bindingset
注释 来指定一个显式绑定集。此注释适用于任何类型的谓词。
例如
bindingset[i]
int multiplyBy4(int i) {
result = i * 4
}
from int i
where i in [1 .. 10]
select multiplyBy4(i)
虽然 multiplyBy4
是一个无限谓词,但上面的 QL 查询 是合法的。它首先使用 bindingset
注释来声明,只要 i
绑定到有限数量的值,谓词 multiplyBy4
就会是有限的。然后它在 i
被限制在范围 [1 .. 10]
的上下文中使用该谓词。
也可以为谓词指定多个绑定集。这可以通过添加多个绑定集注释来完成,例如
bindingset[x] bindingset[y]
predicate plusOne(int x, int y) {
x + 1 = y
}
from int x, int y
where y = 42 and plusOne(x, y)
select x, y
- 以这种方式指定的多个绑定集是相互独立的。上面的示例意味着
- 如果
x
被绑定,则x
和y
被绑定。 - 如果
y
被绑定,则x
和y
被绑定。
- 如果
也就是说,bindingset[x] bindingset[y]
,它声明 x
或 y
中至少有一个必须被绑定,不同于 bindingset[x, y]
,它声明 x
和 y
都必须被绑定。
后者在您想要声明一个带结果的谓词时可能很有用,该谓词接受多个输入参数。例如,以下谓词接受一个字符串 str
并将其截断为不超过 len
个字符的长度
bindingset[str, len]
string truncate(string str, int len) {
if str.length() > len
then result = str.prefix(len)
else result = str
}
然后您可以在一个select 子句中使用它,例如
select truncate("hello world", 5)
数据库谓词¶
您查询的每个数据库都包含表示值之间关系的表。这些表(“数据库谓词”)在 QL 中的处理方式与其他谓词相同。
例如,如果一个数据库包含一个 persons 表,你可以编写 persons(x, firstName, _, age)
来约束 x
、firstName
和 age
分别为该表中行的第一、第二和第四列。
唯一的区别是,你无法在 QL 中定义数据库谓词。它们由底层数据库定义。因此,可用的数据库谓词根据你查询的数据库而有所不同。