函数的定义和调用
我们通过 fn
语法来定义一个命名函数:
## examples/function.av
fn add(x, y) {
return x + y;
}
three = add(1, 2);
println(three);
我们定义了一个函数 add
,它接受两个参数 x
和 y
,返回两者相加的结果。注意到,你并不需要定义参数的类型以及返回值的类型,因为 AviatorScript 是动态类型系统,会根据你实际传入和返回的类型自动转换。所以我们也可以用字符串来调用 add
:
## examples/function.av
s = add('hello', ' world');
println(s);
打印 hello world
。调用函数就是 函数名(参数1,参数2....)
的方式,这一点跟大多数编程语言保持一致。
函数返回值
函数的返回值,可以通过 return
语句来直接返回,它可以带一个返回值,也可以不带。比如我们限定 add
方法只允许执行数字:
## examples/function_return.av
fn add(x, y) {
if type(x) != 'long' || type(y) != 'long' {
throw "unsupported type";
}
x + y
}
println(add(1, 2));
println(add('hello', ' world'));
如果 x 或者 y 不是 long,我们通过 throw
抛出一个异常(具体见异常处理一节),因此这里将打印:
3
Exception in thread "main" com.googlecode.aviator.exception.StandardError: unsupported type
at com.googlecode.aviator.runtime.function.internal.ThrowFunction.call(ThrowFunction.java:30)
at Script_1598449868044_2/1689843956.execute0(Unknown Source)
at com.googlecode.aviator.ClassExpression.executeDirectly(ClassExpression.java:65)
at Lambda_1598449868044_1.call(Unknown Source)
at Script_1598449868039_1/1590550415.execute0(Unknown Source)
at com.googlecode.aviator.ClassExpression.executeDirectly(ClassExpression.java:65)
at Lambda_1598449868039_0.call(Unknown Source)
at com.googlecode.aviator.RuntimeFunctionDelegator.call(RuntimeFunctionDelegator.java:63)
at Script_1598449868034_0/1338823963.execute0(Unknown Source)
at com.googlecode.aviator.ClassExpression.executeDirectly(ClassExpression.java:65)
at com.googlecode.aviator.BaseExpression.execute(BaseExpression.java:136)
at com.googlecode.aviator.Main.main(Main.java:42)
细心的朋友肯定注意到,这里 x +y
没有用到 return
,而是一个普通的表达式,并且没有加上分号 ;
。AviatorScript 如果没有明确的 return
语句,函数默认返回最后一个表达式的值,在这个例子中就是 **x+y**
的值。请注意,不能加上分号,如果加上, x+y;
的值是 nil
。这个规则是向 rust 学习的。关于多行表达式的返回值请参考 3.6 节。
这个例子我们也演示了函数的连续调用 println(add(1, 2));
,连续调用了 add 和 println,基本的规则是先对每个函数的参数求值,然后调用函数并返回结果,这里 add(1, 2)
的结果又作为 println
的参数继续执行。
函数重载
从 5.2 开始,aviatorscript 支持参数个数的函数重载,举个例子:
## examples/function_overload.av
fn join(s1) {
"#{s1}"
}
fn join(s1, s2) {
"#{s1}#{s2}"
}
fn join(s1, s2, s3) {
"#{s1}#{s2}#{s3}"
}
p(join("hello"));
p(join("hello", " world"));
p(join("hello", " world", ", aviator"));
join
定义了三个版本的分支函数,分别接受 1 个、2 个和 3 个参数,返回拼接后的字符串,然后我们尝试测试了调用它们,输出:
hello
hello world
hello world, aviator
null
同名的函数按照参数个数来重载,不支持类型重载,后面定义的相同参数的分支将覆盖原来的,比如我们继续给 join 重新定义一个参数的分支,让他返回 nil:
## redefined join(s)
fn join(s1) {
nil
}
p(join("hello"));
p(join("hello", " world"));
p(join("hello", " world", ", aviator"));
再次执行将输出:
null
hello world
hello world, aviator
单参数的分支将返回 nil 并打印为 null。
不定参数
同样, 从 5.2 版本开始,aviatorscript 也支持了不定参数个数的函数定义,跟 java 的要求类似,也要求可变参数只能出现在参数列表的最后一个位置,并且用 &
作为前缀,比如我们定义一个使用间隔符拼接字符串的 join 函数:
## examples/function_varargs.av
fn join(sep, &args) {
let s = "";
let is_first = true;
for arg in args {
if is_first {
s = s + arg;
is_first = false;
}else {
s = s + sep + arg;
}
}
return s;
}
p(join(" ", "a", "b", "c"));
p(join(",", "a", "b", "c", "d"));
p(join(",", "a"));
第一个参数是间隔符 sep
,第二个参数是可变的参数 args
,通过符号 &
来表明它是一个可变参数,最终会将可变的参数收集成 List 并传入 join
函数,然后我们通过 for
循环遍历这些参数并做字符串拼接:
a b c
a,b,c,d
a
Unpacking Arguments(参数解包)
很多时候,你想传入的参数可能是一个数组,而函数接受的却是拆开的一个一个的参数,这个时候,就可以用上 unpacking arguments,语法设计和 python 类似:
## examples/unpacking_args.av
fn add(a, b) {
a + b
}
let list = seq.list(1, 2);
p(add(*list));
add
函数接受两个参数 a 和 b,而 list 是一个链表,如果想调用 add,原始的方式是自己拆开 add(list[0], list[1])
,特别的不方便,通过 unpacking arguments 支持,只要给 list 前面加上 *
号,也就是 *list
就可以自动帮你“展开”。
unpacking 可以发生在参数的任何位置:
fn test(a, b, c, d) {
a * b + c * d
}
let a = tuple(1, 2);
let list = seq.list(3, 4);
p(test(*a, *list));
四个参数,通过两个 sequence 解开来填充。计算结果为 1*2 + 3*4
等于 14
。
可变参数本质上也是数组,因此也可以用 unpacking arguments 的方式来调用:
fn average(&args) {
return sum(*args) / count(args);
}
fn sum(&args) {
s = 0.0;
for arg in args {
s = s + arg;
}
return s;
}
p(average(1, 2, 3, 4));
sum
接受一个可变参数数组,因此在 average
调用 sum 的时候,需要 unpacking,否则 sum 接收到的是一个数组组成的数组。上述结果为 2.5
。