Skip to content

Commit b94680b

Browse files
authored
Merge pull request #16 from codefuse-ai/lhk_dev
[doc] Update documents about `=` operator and function call in `output`
2 parents 0a407ca + 72162ca commit b94680b

File tree

1 file changed

+148
-34
lines changed

1 file changed

+148
-34
lines changed

doc/4_godelscript_language.md

Lines changed: 148 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ GödelScript 编译器主要应用场景为:
2121

2222
1. 面向用户编写简单或复杂查询,提供更便捷的写法,提高编写查询的效率;
2323
2. 提供严格类型检查与类型推导,给予更智能的代码修改提示;
24-
3. 提供严格的 [ungrounded](#ungrounded-error) 检测,避免容易触发的 Soufflé Ungrounded Error;
24+
3. 提供严格的 [ungrounded(未赋值/未绑定)](#ungrounded-error-未赋值未绑定错误) 检测,避免触发 Soufflé Ungrounded Error;
2525
4. Language Server 以及 IDE Extension 支持。
2626

2727
### 基本程序构成
@@ -105,11 +105,9 @@ GödelScript 采用类 C 语言的注释方式。
105105

106106
#### `main` 函数
107107

108-
GödelScript 查询脚本可以包含`main`函数,该函数无返回值。
108+
GödelScript 查询脚本可以包含`main`函数,该函数无返回值。在不实现`main`函数,且没有写 query 声明的情况下,程序不会输出。
109109

110-
`main`中可以多次使用`output(...)`来表明要输出多个查询结果。
111-
112-
`main`函数只允许使用`output`,其他语句会导致编译错误。
110+
更多详细内容请看 [`main`函数](#gödelscript-main-函数)
113111

114112
```rust
115113
fn main() {
@@ -118,11 +116,9 @@ fn main() {
118116
}
119117
```
120118

121-
在不实现`main`函数,且没有写 query 声明的情况下,程序不会输出。
122-
123119
### 基础类型和编译器内建函数
124120

125-
GödelScript 包含基础类型`int``string``bool`属于基础类型,但是不能作为值存储。
121+
GödelScript 包含基础类型`int` `string``bool`属于基础类型,但是不能作为值存储。
126122

127123
#### `int`类型 native 函数
128124

@@ -204,7 +200,7 @@ GödelScript 包含基础类型`int``string`,`bool`属于基础类型,但是
204200
| to<T> | (self) -> T | 转换到其他类型的 schema,采用 duck type 检测。 |
205201
| is<T> | (self) -> bool | 判断是否可以是其他类型的 schema,采用 duck type 检测。如果自身 schema 有主键,则底层只会通过主键判断是否可以是其他类型。 |
206202
| key_eq | (self, T) -> bool | 检查两个 schema 实例的主键是否相等。 |
207-
| key_neq | (self, T) -> bool | 检查两个 schema 实例的主键是否****相等|
203+
| key_neq | (self, T) -> bool | 检查两个 schema 实例的主键是否不等|
208204

209205
schema native 函数实例:
210206

@@ -232,42 +228,101 @@ fn convert() -> *ElementParent {
232228

233229
### 函数
234230

235-
#### `main` 函数
231+
#### GödelScript `main` 函数
236232

237-
在上文中已经提及,该函数是 GödelScript 中唯一不需要声明返回值的函数
233+
`main`函数是 GödelScript 中唯一不声明返回值的函数。`main`函数只允许使用`output`,其他语句会导致编译错误;多次使用`output(...)`可以输出多个查询结果,查询结果会分表显示,表名即为`output`中调用的查询函数的函数名
238234

239235
#### 查询函数
240236

241237
查询函数的返回值类型推荐为`bool`,需要输出查询结果时,需要使用`output()`函数。
242238

239+
在`output()`中调用的查询函数不再是常规思路中的用传参调用函数。参数列表在此时会变化为输出表的表结构,下面是两个查询函数的应用实例:
240+
241+
1. 单表`output`
242+
243+
单表`output`特指在`main`函数中,只使用一次`output`来输出。
244+
245+
```rust
246+
fn example(a: int, b: string) -> bool {...}
247+
248+
fn main() {
249+
output(example()) // 此时参数列表变为输出表结构,不需要传参
250+
}
251+
```
252+
253+
对应的输出表结构为:
254+
255+
```json
256+
[
257+
{"a": 0, "b": "xxx"},
258+
{"a": 1, "b": "xxx"}
259+
]
260+
```
261+
262+
2. 多表`output`
263+
264+
多表`output`是指在`main`函数中,使用多次`output`来输出。在这种情况下,输出数据会附带对应的表名。
265+
266+
```rust
267+
fn example0(a: int, b: string) -> bool {...}
268+
fn example1(a: string, b: int) -> bool {...}
269+
270+
fn main() {
271+
output(example0())
272+
output(example1())
273+
}
274+
```
275+
276+
对应的输出表结构为:
277+
278+
```json
279+
{
280+
"example0":[
281+
{"a": 0, "b": "xxx"},
282+
{"a": 1, "b": "xxx"}
283+
],
284+
"example1":[
285+
{"a": "xxx", "b": 0},
286+
{"a": "xxx", "b": 1}
287+
]
288+
}
289+
```
290+
291+
下面是一个比较详细的例子,在这个例子中,我们直接构造了两组数据并输出。在下列代码中,需要注意的是:
292+
293+
1. GödelScript 中,布尔值可以使用`true`和`false`关键字。
294+
295+
2. `=`符号在 GödelScript 中是比较特殊的符号,不能用常规的编程语言的思路来理解。GödelScript 是一种 Datalog 语言。在这里,`=`符号同时具备两种语义,一个是 __赋值__ 一个是 __判等__。详情可看[`=`运算符](#赋值和判等运算符)。
296+
297+
3. 在这个例子的条件语句中,`a`和`b`均使用了`=`的赋值语义,因为`int`和`string`类型参数在函数体中被认为是`ungrounded(未赋值/未绑定)`,必须要被赋值才能使用。
298+
299+
4. `=`赋值语句的返回值是`true`。
300+
243301
```rust
244-
fn myQuery(a: int, b: string) -> bool {
245-
if (a = 1 && b = "hello") {
302+
fn example(a: int, b: string) -> bool {
303+
// = 符号同时具有赋值和比较的功能,取决于此时的左值是否已经“被赋值”
304+
// 这里的 a 和 b 所用的 = 符号均是赋值语义
305+
if (a = 1 && b = "1") {
306+
// GödelScript 使用关键字 true 和 false 来表示布尔值
246307
return true
247308
}
248-
if (...) {
249-
...
309+
if (a = 2 && b = "2") {
310+
return true
250311
}
251-
...
252312
}
253313

254314
fn main() {
255-
output(myQuery()) // 这里无需填写传入参数
315+
output(example())
256316
}
257317
```
258318

259-
`output()`会根据该函数的参数列表格式来输出表结构。所以在`main`中使用时,`output()`并不需要你提供这个函数的传入参数。对应的输出表结构大致如下
319+
预期的输出结果应该为
260320

261-
| a | b |
262-
| --- | --- |
263-
| 1 | "hello" |
264-
| ... | ... |
265-
266-
GödelScript 使用`true`和`false`关键字来代表返回的`bool`类型结果:
267-
268-
```rust
269-
return true
270-
return false
321+
```json
322+
[
323+
{"a": 1, "b": "1"},
324+
{"a": 2, "b": "2"}
325+
]
271326
```
272327

273328
#### 普通函数
@@ -352,11 +407,70 @@ if (f.getName().contains("util") || f.getName().contains("com")) {
352407

353408
条件可以使用这些逻辑运算符进行连接:`!`取反,`||`或,`&&`与。
354409

355-
条件中的比较运算符:`>`大于,`<`小于,`>=`大于等于,`<=`小于等于,`=`等于,`!=`不等于。
410+
条件中的比较运算符:`>`大于,`<`小于,`>=`大于等于,`<=`小于等于,`=`等于或者赋值,`!=`不等于。
356411

357412
常规算术运算可以使用如下运算符:`+`加法,`-`减法/取负,`*`乘法,`/`除法。
358413

359-
**注意:比较运算符中的**`**=**`**在左侧变量没有被绑定数据时,会执行绑定操作并返回**`**true**`**,类似于赋值操作。**
414+
##### 赋值和判等运算符`=`
415+
416+
`=`符号在 GödelScript 中具有两种不同的语义:赋值和判等,具体的语义需要分情况进行讨论:
417+
418+
1. 赋值
419+
420+
赋值一般出现在`int` `string`这类基础类型的变量参数上,这类变量作为函数的参数出现时,一般被认为是未赋值的。而具有这类变量的函数被调用时,传入的参数,实际上是作为筛选条件存在。
421+
422+
```rust
423+
fn example(a: int) -> bool {
424+
// 这里比较反直觉,在过程式语言中,这里通常会被认为是判断 a == 1
425+
// 但是在 datalog 方言中,datalog 的每个函数实际上都是在算一个中间表 (view)
426+
// 所以这个函数本质上是生成了一个 view,数据为 [{"a": 1}]
427+
return a = 1 // assign a = 1
428+
}
429+
430+
fn test() -> bool {
431+
// 这里看似是在通过传参让 a = 2,实际上并不是
432+
// example() 自己会返回 view: [{"a": 1}]
433+
// 然后通过 a = 2 来约束结果,可以看到,我们这里没有拿到任何结果
434+
// 所以返回了 false
435+
return example(2) // false
436+
}
437+
```
438+
439+
2. 判等
440+
441+
对于 schema 类型来说,任何一个 schema 背后都有一个全集,所以参数列表中的 schema 类型一般被认为是已经被赋值的。对于已经赋值的变量来说,`=`就是判等操作。
442+
443+
```rust
444+
// 声明 schema
445+
schema A {...}
446+
447+
// 实现 schema 的成员函数
448+
impl A {
449+
// 这里定义了 schema A 的全集
450+
@data_constraint
451+
pub fn __all__() -> *A {...}
452+
}
453+
454+
fn example(a: A) -> bool {
455+
for(temp in A::__all__()) {
456+
if (a = temp) {
457+
return true
458+
}
459+
}
460+
}
461+
```
462+
463+
同样,对于中间声明的有初始值的`int`或者`string`,`=`也是判等操作。
464+
465+
```rust
466+
fn example() -> bool {
467+
let (a = 1) { // assign a = 1
468+
if (a = 1) { // compare a = 1
469+
return true
470+
}
471+
}
472+
}
473+
```
360474

361475
#### match 语句
362476

@@ -944,9 +1058,9 @@ fn class_method(className: string, methodName: string, methodSignature: string)
9441058
}
9451059
```
9461060

947-
### Ungrounded Error
1061+
### Ungrounded Error: 未赋值/未绑定错误
9481062

949-
GödelScript 会将未与集合绑定的符号判定为`ungrounded`。基本判定规则为:
1063+
GödelScript 会将未与数据绑定的符号判定为`ungrounded(未赋值/未绑定)`。基本判定规则为:
9501064

9511065
- 未初始化的/未被使用的/未与集合绑定的符号
9521066
- 未被绑定的`int``string`参数
@@ -2086,9 +2200,9 @@ e, p 的笛卡尔积就变成了 e, i 的笛卡尔积,从运算的层面来看
20862200
### 不要滥用`@inline`/必须用`@inline`的优化策略
20872201

20882202
inline 函数的底层机制是在**调用处展开**,如果该函数不存在大量的 schema 传参,并且在很多位置都被调用,inline 可能会导致**编译结果膨胀且重复计算次数指数级增加**,有时反而不利于减少运行时间。
2089-
如果存在必须要使用 inline 的情况 (比如规避 ungrounded),但是使用之后反而出现运行速度变慢的情况,可以采取将内嵌语句拆分为 predicate 的方式来避免展开导致的编译结果膨胀。
2203+
如果存在必须要使用 inline 的情况 (比如规避`ungrounded`),但是使用之后反而出现运行速度变慢的情况,可以采取将内嵌语句拆分为 predicate 的方式来避免展开导致的编译结果膨胀。
20902204

2091-
下面的例子中,`getValueByAttributeNameByDefaultValue`为了避免`attributeName`被识别为 ungrounded 所以标注 inline,后续在 if 分支中添加了一个条件语句,但是导致了执行时间从 3 秒变成 35 秒:
2205+
下面的例子中,`getValueByAttributeNameByDefaultValue`为了避免`attributeName`被识别为`ungrounded`所以标注`inline`,后续在 if 分支中添加了一个条件语句,但是导致了执行时间从 3 秒变成 35 秒:
20922206

20932207
```rust
20942208
impl XmlElementBase {

0 commit comments

Comments
 (0)