函数的定义和调用
我们通过 fn 语法来定义一个命名函数:
## examples/function.avfn add(x, y) {return x + y;}three = add(1, 2);println(three);
我们定义了一个函数 add ,它接受两个参数 x 和 y ,返回两者相加的结果。注意到,你并不需要定义参数的类型以及返回值的类型,因为 AviatorScript 是动态类型系统,会根据你实际传入和返回的类型自动转换。所以我们也可以用字符串来调用 add :
## examples/function.avs = add('hello', ' world');println(s);
打印 hello world 。调用函数就是 函数名(参数1,参数2....) 的方式,这一点跟大多数编程语言保持一致。
函数返回值
函数的返回值,可以通过 return 语句来直接返回,它可以带一个返回值,也可以不带。比如我们限定 add 方法只允许执行数字:
## examples/function_return.avfn 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 抛出一个异常(具体见异常处理一节),因此这里将打印:
3Exception in thread "main" com.googlecode.aviator.exception.StandardError: unsupported typeat 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.avfn 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 个参数,返回拼接后的字符串,然后我们尝试测试了调用它们,输出:
hellohello worldhello world, aviatornull
同名的函数按照参数个数来重载,不支持类型重载,后面定义的相同参数的分支将覆盖原来的,比如我们继续给 join 重新定义一个参数的分支,让他返回 nil:
## redefined join(s)fn join(s1) {nil}p(join("hello"));p(join("hello", " world"));p(join("hello", " world", ", aviator"));
再次执行将输出:
nullhello worldhello world, aviator
单参数的分支将返回 nil 并打印为 null。
不定参数
同样, 从 5.2 版本开始,aviatorscript 也支持了不定参数个数的函数定义,跟 java 的要求类似,也要求可变参数只能出现在参数列表的最后一个位置,并且用 & 作为前缀,比如我们定义一个使用间隔符拼接字符串的 join 函数:
## examples/function_varargs.avfn 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 ca,b,c,da
Unpacking Arguments(参数解包)
很多时候,你想传入的参数可能是一个数组,而函数接受的却是拆开的一个一个的参数,这个时候,就可以用上 unpacking arguments,语法设计和 python 类似:
## examples/unpacking_args.avfn 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 。
