函数¶
函数分类与纯度¶
Lumos 根据副作用和纯度将函数分为三类,分别使用不同的关键字定义:
def:纯函数。输入相同则输出必相同,无任何副作用。只能调用其他def函数。fun:逻辑纯函数。内部可以有局部副作用(如局部变量修改、内存分配),但对外部环境无影响。可以调用fun和def。act:副作用函数。可以执行 I/O、修改全局状态等。可以调用act、fun和def。必须声明所需的权限。
纯度检查示例¶
def add(int a, int b) -> int = a + b;
fun calculate() {
val x = add(1, 2); // OK: fun can call def
// println("Result: {x}"); // Error: fun cannot call act `println`
}
act[io.out] main() {
calculate(); // OK: act can call fun
println("Done");
}
注意:
act函数可以使用方括号[]声明所需的权限。如果不需要任何权限,直接使用act即可。
此外还有多个修饰符:
ovl:允许重载。默认情况下函数不可重载。若要重载,该名称对应的所有定义都必须显式带有此关键字。unsafe fun:将可回滚副作用标记为逻辑纯,允许在fun中使用,但必须满足以下约束:- 必须显式写权限列表(即使为空)。
- 必须提供回滚块(在定义处强制),回滚动作需并发安全。
- 仅当权限列表为空(
unsafe fun[])时,才允许被fun调用;否则编译错误。 - 除闭包外,调用
unsafe fun不会触发自动降级(必须手动调整为act)。
once? act/once! act:承诺该动作只执行一次。once?在再次调用时忽略,once!在再次调用时报错。在第一次显式调用时执行。性能注意:
once包含编译期和运行时的双重检查,应避免在性能敏感的场景(如高频循环)中使用。
调试伪函数 dbg¶
为了方便调试而不破坏函数的纯度,Lumos 提供了一个特殊的伪函数 dbg:
- 全场景可用:可以在
def、fun、act等任何地方调用。 - 编译期输出:如果在编译期求值(如
lit初始化)中调用,编译器会将信息输出到stderr。 - 运行时输出:在程序运行时调用,会将信息输出到
stderr。 - 不破坏纯度:调用
dbg不会被视为副作用,因此不会导致def或fun降级为act。
def add(i32 a, i32 b) -> i32 {
dbg(`adding $a and $b`); // 允许在纯函数中使用
return a + b;
}
函数声明¶
// 声明一个函数
[ovl] def 函数名 with[捕获列表](参数列表) -> 返回类型;
[ovl] fun 函数名 with[捕获列表](参数列表) -> 返回类型;
[ovl] act 函数名 with[捕获列表](参数列表) -> 返回类型;
ovl:可选,标记该函数支持重载。- 函数名:函数的标识符,用于在程序中调用该函数。
- 捕获列表:可选项,使用
with[...]指定函数中使用的外部变量的捕获方式。 - 参数列表:可选项,包含函数的输入参数列表。
- 返回类型:可选项,指定函数返回值的类型。
函数定义¶
// 定义一个函数
def 函数名 with[捕获列表](参数列表) -> 返回类型 {函数体}
- 函数名、捕获列表、参数列表和返回类型的含义同函数声明。
- 函数体:包含了函数的实际实现代码。
unsafe fun 需要在定义处提供回滚块(函数体后尾随):
unsafe fun[sys.mem] alloc_buf(usize n) -> ptr {
val p = malloc(n);
return p;
} rollback {
free(p);
}
回滚块可以访问函数体内的局部变量,语义等同于紧随 return 之后的代码;若存在多个 return,则只有在所有分支都可达的变量才允许在回滚块中使用。
单语句函数定义¶
// 定义一个单语句的函数
def 函数名 with[捕获列表](参数列表) = 表达式;
- 适用于只包含一条语句的函数,可以将函数体直接定义为一个表达式。
- 表达式的计算结果将作为函数的返回值。
Lambda 表达式¶
// 定义一个 lambda 表达式
def with[捕获列表](参数列表) -> 返回类型 {函数体}
fun with[捕获列表](参数列表) -> 返回类型 {函数体}
act with[捕获列表](参数列表) -> 返回类型 {函数体}
- Lambda 表达式同样需要指定纯度关键字,且其纯度必须与所在作用域匹配:
def作用域只能定义def,fun作用域只能定义fun,act作用域可定义act。 - 不需要指定函数名,直接使用捕获列表、参数列表、返回类型和函数体定义 Lambda 函数。
例外:
def/fun可以接受act闭包作为参数,但只有在调用点处于act上下文时才合法。此时编译器会自动将原本的def/fun降级为act(无需手动修改)。
返回类型推断¶
- 当函数体中所有
return处的类型一致时,可以省略返回类型。 - 若函数体中没有
return语句,则返回类型推断为unit。
不返回函数¶
对于永远不会返回的函数(如调用了 exit 或无限循环),使用 - 标记:
act[io.out, sys.proc] fatal_error(string msg) - {
println(msg);
exit(1);
}
捕获列表¶
- 捕获列表用于指定函数中引用外部变量的方式。
- 使用
with[...]表示捕获列表,避免与act[...]权限列表冲突。 - 可以省略捕获列表:默认仅捕获
val/imv/lit等不可变变量,并且为引用捕获(不发生拷贝、不分配内存)。 - 可变变量(如
var)不会被自动捕获,必须在with[...]中显式声明捕获方式。 - 支持引用捕获和值捕获两种方式。
参数列表¶
- 参数列表用于指定函数的输入参数。
- 可以省略参数列表,表示函数没有参数。
- 当函数没有参数时,参数列表可以是
(),但不能像 C 语言那样写作(unit)。
参数范围限定 where¶
我们用 where 关键字对函数参数进行范围限定:
def sqrt(f32 x where x >= 0.0) -> f32 {
// ...
}
where 后接一个布尔表达式,该表达式会在调用时进行检查,如果不满足则抛出异常。同时作为优化手段也会在编译期进行检查,如果能证明调用时该表达式总为真则不会生成运行时检查代码。
where 后的表达式必须为纯表达式,且只能使用参数变量和常量。
成员函数修饰符¶
在成员函数中,可以使用 @ 符号标记函数对对象成员的访问权限:
@ro(Read-Only):只能读成员变量,不能写。@wo(Write-Only):只能写成员变量,不能读。@rw(Read-Write):可读可写成员变量(默认)。@rx(Read-Execute):可读可执行成员变量,不能写。
class MyClass {
i32 value;
def@ro get_value() -> i32 {
return value; // 允许读
}
act@wo set_value(i32 v) -> unit {
value = v; // 允许写
}
}
示例¶
def add(i32 a, i32 b) -> i32 {
return a + b;
}
act[io.out] main() -> i32 {
return add(1, -1);
}
异步函数 async¶
Lumos 支持异步编程,通过 async 关键字修饰 fun 或 act:
async fun:异步逻辑纯函数。用于执行耗时的计算任务,虽然涉及任务调度,但对外部逻辑保持透明,不产生 I/O 或全局状态修改。async act:异步副作用函数。允许在异步执行过程中进行 I/O 操作或修改全局状态。
注意:def 不支持异步,因为纯函数要求立即返回确定的结果。
// 异步计算(逻辑纯)
async fun compute_pi(i32 precision) -> f64 {
// 耗时计算
return result;
}
// 异步 IO(副作用)
async act[net.http.client] fetch_url(string url) -> string {
val content = await network.get(url);
return content;
}
调用简写¶
对于没有参数的函数,可以直接使用函数名来调用。
如果有以下函数:
fun rand() -> i32 {
@static
var i32 seed = 1;
seed = seed * 114514 + 1919810;
return seed;
}
我们就可以:
println(rand());
println(rand);
对于单个参数的函数,我们可以省略括号:
println("Hello world!");
println "Hello world!";
规范¶
我们明确:一个函数只应该做一件事情,这样可以使代码更加清晰。
函数一般不超过 20 行语句,超过的话应该考虑拆分。
函数尽量不依赖全局状态,这样可以使函数更加独立,也便于进行单元测试。
函数的参数应该尽量少,大多数不超过 3 个,少数可以达到 6 个,超过的话应该考虑拆分。对于输入状态特别多的函数,应当使用结构体引用传参。
函数的参数应该尽量避免使用未进行限制的指针,除非有必要。
相关内容:权限系统见 副作用权限系统。