函数的定义和调用

我们通过 fn 语法来定义一个命名函数:

  1. ## examples/function.av
  2. fn add(x, y) {
  3. return x + y;
  4. }
  5. three = add(1, 2);
  6. println(three);

我们定义了一个函数 add ,它接受两个参数 xy ,返回两者相加的结果。注意到,你并不需要定义参数的类型以及返回值的类型,因为 AviatorScript 是动态类型系统,会根据你实际传入和返回的类型自动转换。所以我们也可以用字符串来调用 add

  1. ## examples/function.av
  2. s = add('hello', ' world');
  3. println(s);

打印 hello world 。调用函数就是 函数名(参数1,参数2....) 的方式,这一点跟大多数编程语言保持一致。

函数返回值

函数的返回值,可以通过 return 语句来直接返回,它可以带一个返回值,也可以不带。比如我们限定 add 方法只允许执行数字:

  1. ## examples/function_return.av
  2. fn add(x, y) {
  3. if type(x) != 'long' || type(y) != 'long' {
  4. throw "unsupported type";
  5. }
  6. x + y
  7. }
  8. println(add(1, 2));
  9. println(add('hello', ' world'));

如果 x 或者 y 不是 long,我们通过 throw 抛出一个异常(具体见异常处理一节),因此这里将打印:

  1. 3
  2. Exception in thread "main" com.googlecode.aviator.exception.StandardError: unsupported type
  3. at com.googlecode.aviator.runtime.function.internal.ThrowFunction.call(ThrowFunction.java:30)
  4. at Script_1598449868044_2/1689843956.execute0(Unknown Source)
  5. at com.googlecode.aviator.ClassExpression.executeDirectly(ClassExpression.java:65)
  6. at Lambda_1598449868044_1.call(Unknown Source)
  7. at Script_1598449868039_1/1590550415.execute0(Unknown Source)
  8. at com.googlecode.aviator.ClassExpression.executeDirectly(ClassExpression.java:65)
  9. at Lambda_1598449868039_0.call(Unknown Source)
  10. at com.googlecode.aviator.RuntimeFunctionDelegator.call(RuntimeFunctionDelegator.java:63)
  11. at Script_1598449868034_0/1338823963.execute0(Unknown Source)
  12. at com.googlecode.aviator.ClassExpression.executeDirectly(ClassExpression.java:65)
  13. at com.googlecode.aviator.BaseExpression.execute(BaseExpression.java:136)
  14. 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 支持参数个数的函数重载,举个例子:

  1. ## examples/function_overload.av
  2. fn join(s1) {
  3. "#{s1}"
  4. }
  5. fn join(s1, s2) {
  6. "#{s1}#{s2}"
  7. }
  8. fn join(s1, s2, s3) {
  9. "#{s1}#{s2}#{s3}"
  10. }
  11. p(join("hello"));
  12. p(join("hello", " world"));
  13. p(join("hello", " world", ", aviator"));

join 定义了三个版本的分支函数,分别接受 1 个、2 个和 3 个参数,返回拼接后的字符串,然后我们尝试测试了调用它们,输出:

  1. hello
  2. hello world
  3. hello world, aviator
  4. null

同名的函数按照参数个数来重载,不支持类型重载,后面定义的相同参数的分支将覆盖原来的,比如我们继续给 join 重新定义一个参数的分支,让他返回 nil:

  1. ## redefined join(s)
  2. fn join(s1) {
  3. nil
  4. }
  5. p(join("hello"));
  6. p(join("hello", " world"));
  7. p(join("hello", " world", ", aviator"));

再次执行将输出:

  1. null
  2. hello world
  3. hello world, aviator

单参数的分支将返回 nil 并打印为 null。

不定参数

同样, 从 5.2 版本开始,aviatorscript 也支持了不定参数个数的函数定义,跟 java 的要求类似,也要求可变参数只能出现在参数列表的最后一个位置,并且用 & 作为前缀,比如我们定义一个使用间隔符拼接字符串的 join 函数:

  1. ## examples/function_varargs.av
  2. fn join(sep, &args) {
  3. let s = "";
  4. let is_first = true;
  5. for arg in args {
  6. if is_first {
  7. s = s + arg;
  8. is_first = false;
  9. }else {
  10. s = s + sep + arg;
  11. }
  12. }
  13. return s;
  14. }
  15. p(join(" ", "a", "b", "c"));
  16. p(join(",", "a", "b", "c", "d"));
  17. p(join(",", "a"));

第一个参数是间隔符 sep ,第二个参数是可变的参数 args ,通过符号 & 来表明它是一个可变参数,最终会将可变的参数收集成 List 并传入 join 函数,然后我们通过 for 循环遍历这些参数并做字符串拼接:

  1. a b c
  2. a,b,c,d
  3. a

Unpacking Arguments(参数解包)

很多时候,你想传入的参数可能是一个数组,而函数接受的却是拆开的一个一个的参数,这个时候,就可以用上 unpacking arguments,语法设计和 python 类似:

  1. ## examples/unpacking_args.av
  2. fn add(a, b) {
  3. a + b
  4. }
  5. let list = seq.list(1, 2);
  6. p(add(*list));

add 函数接受两个参数 a 和 b,而 list 是一个链表,如果想调用 add,原始的方式是自己拆开 add(list[0], list[1]) ,特别的不方便,通过 unpacking arguments 支持,只要给 list 前面加上 * 号,也就是 *list 就可以自动帮你“展开”。

unpacking 可以发生在参数的任何位置:

  1. fn test(a, b, c, d) {
  2. a * b + c * d
  3. }
  4. let a = tuple(1, 2);
  5. let list = seq.list(3, 4);
  6. p(test(*a, *list));

四个参数,通过两个 sequence 解开来填充。计算结果为 1*2 + 3*4 等于 14

可变参数本质上也是数组,因此也可以用 unpacking arguments 的方式来调用:

  1. fn average(&args) {
  2. return sum(*args) / count(args);
  3. }
  4. fn sum(&args) {
  5. s = 0.0;
  6. for arg in args {
  7. s = s + arg;
  8. }
  9. return s;
  10. }
  11. p(average(1, 2, 3, 4));

sum 接受一个可变参数数组,因此在 average 调用 sum 的时候,需要 unpacking,否则 sum 接收到的是一个数组组成的数组。上述结果为 2.5