匿名函数 lambda

你可以通过 lambda 语法定义一个匿名函数:

  1. ## examples/lambda.av
  2. let three = (lambda (x,y) -> x + y end)(1, 2);
  3. println(three);

lambda (x,y) -> x + y end 定义了一个匿名函数,接受参数 x 和 y,返回两者的和。

匿名函数的基本定义形式是

  1. lambda (参数1,参数2...) -> 参数体表达式 end

定义了匿名函数后,我们直接传入参数 1 和 2,两者相加的结果赋值给 three 变量并打印,也就是结果 3

匿名函数可以用赋值给一个变量:

  1. ## examples/lambda.av
  2. let add = lambda (x,y) ->
  3. x + y
  4. end;
  5. three = add(1, 2);
  6. println(three);

我们将上述匿名函数赋值给了变量 add ,其实相当于定义了一个 add 函数,然后调用 add(1, 2) ,同样返回结果 3

从 5.2.4 开始,匿名函数的定义也可以用 fn 语法:

  1. let add = fn(x, y) { x + y};
  2. p(add(1, 2));

事实上,上一节通过 fn 定义函数本质上就是类似的步骤,先用 lambda 定义一个匿名函数,然后赋值给函数名指定的变量

函数是一等公民 (First Class)

在 AviatorScript 中,函数也是一种类型,可以作为参数来传递,可以作为函数的返回值等等。

  1. ## examples/function_first_class.av
  2. fn square(x) {
  3. return x * 2;
  4. }
  5. let add = lambda(x, y, f) ->
  6. f(x) + f(y)
  7. end;
  8. let add_n = lambda(x) ->
  9. lambda(y) ->
  10. x + y
  11. end
  12. end;
  13. println(type(square));
  14. println(type(add));
  15. println(type(add_n));

我们通过 fn 和 lambda 分别定义了三个函数,通过 type 检测他们的类型:

  1. function
  2. function
  3. function

都是 function 函数类型。

这里的 add 函数接受的第三个参数 f 也是一个函数,我们用它调用 x 和 y,然后再相加,可见函数是可以作为参数来使用:

  1. let s = add(1, 2, square);
  2. println(s);

我们将 square 函数作为 f 传入到 add,执行的结果就是 square(1) + square(2) ,结果等于 6:

  1. 6

add_n 演示了函数作为返回值的情况, add_n 接受一个参数 x,然后返回一个匿名函数:

  1. lambda(y) ->
  2. x + y
  3. end

这个匿名函数接受另一个参数 y,他将 x 和 y 相加并返回:

  1. let add_3 = add_n(3);
  2. println(type(add_3));
  3. println(add_3(1));
  4. println(add_3(99));
  5. println(add_3(' test'));

我们将 add_n(3) 的返回值赋值给了变量 add_3 ,他的类型是 function,任何时候调用 add_3 函数,它都将和 3 相加,并返回结果:

  1. function
  2. 4
  3. 102
  4. 3 test

这个例子也演示了闭包,我们在下一节谈到。

闭包 Closure

AviatorsScript 的函数都支持闭包 closure,函数将捕获当前的上下文环境,哪怕在脱离这个环境的上下文中仍然可以访问到,一个经典的例子,定义一个计数器:

  1. ## examples/closure.av
  2. let counter = lambda() ->
  3. let c = 0;
  4. lambda() ->
  5. let result = c;
  6. c = c + 1;
  7. return result;
  8. end
  9. end;
  10. let c1 = counter();
  11. let c2 = counter();
  12. println("test c1...");
  13. for i in range(0, 10) {
  14. x = c1();
  15. println(x);
  16. }
  17. println("test c2...");
  18. for i in range(0, 10) {
  19. x = c2();
  20. println(x);
  21. }

counter 是一个函数,它首先初始化了一个局部变量 c,然后返回结果是另一个匿名函数,这个结果匿名函数每次调用返回 c 的值,并递增。

接下来我们调用 counter 两次,赋值给了两个变量 c1 和 c2,分别调用 10 次,输出:

  1. test c1...
  2. 0
  3. 1
  4. 2
  5. 3
  6. 4
  7. 5
  8. 6
  9. 7
  10. 8
  11. 9
  12. test c2...
  13. 0
  14. 1
  15. 2
  16. 3
  17. 4
  18. 5
  19. 6
  20. 7
  21. 8
  22. 9

可见, c1 和 c2 的值完全独立,分别用于自增计数,互不干扰。

c 在函数 counter 中定义,理论上说局部变量在 counter 函数返回后就“销毁”了,但是匿名的结果函数却“捕获”(closure over)了局部变量 c,哪怕在 counter 返回后,仍然可以继续访问到变量 c ,这就称之为闭包(closure), c 就是所谓自由变量。

闭包模拟 OOP

闭包可以“保存”状态,因此可以用于模拟 OOP:

  1. ## examples/closure_oop.av
  2. ## a function to return a rectangle instance.
  3. fn rectangle(x, y) {
  4. let r = seq.map("x", x, "y", y);
  5. r.area = lambda() ->
  6. return r.x * r.y;
  7. end;
  8. r.circum = lambda() ->
  9. return 2 * (r.x + r.y);
  10. end;
  11. return r;
  12. }
  13. let s1 = rectangle(3, 4);
  14. println("s1 info:");
  15. println(s1.x);
  16. println(s1.y);
  17. println(s1.area());
  18. println(s1.circum());
  19. let s2 = rectangle(9, 10);
  20. println("s2 info:");
  21. println(s2.x);
  22. println(s2.y);
  23. println(s2.area());
  24. println(s2.circum());

rectangle 函数用于返回一个 x 和 y 长和宽定义的长方形对象,它有两个方法 areacircum 分别用于计算长方形的面积和周长。这里我们用一个 map 来模拟对象,将 x 和 y 作为 key 放入 map,然后定义了两个匿名函数分别赋值给了 r.arear.circum ,这就相当于定义了对象的方法,最后返回结果 map。两个匿名函数是闭包,捕获了自由变量 r。

接下来我们定义了两个长方形 s1 和 s2,并且打印了他们的基本信息。可以看到类似 s1.xs1.area() 这样的调用,接近于面向对象的使用方式。得益于闭包,两个对象的信息是完全独立的:

  1. s1 info:
  2. 3
  3. 4
  4. 12
  5. 14
  6. s2 info:
  7. 9
  8. 10
  9. 90
  10. 38

甚至,我们可以修改对象的属性:

  1. s2.x = 100;
  2. s2.y = 200;
  3. println("s2 info after setting:");
  4. println(s2.x);
  5. println(s2.y);
  6. println(s2.area());
  7. println(s2.circum());

输出:

  1. s2 info after setting:
  2. 100
  3. 200
  4. 20000
  5. 600