匿名函数 lambda
你可以通过 lambda 语法定义一个匿名函数:
## examples/lambda.avlet three = (lambda (x,y) -> x + y end)(1, 2);println(three);
lambda (x,y) -> x + y end 定义了一个匿名函数,接受参数 x 和 y,返回两者的和。
匿名函数的基本定义形式是
lambda (参数1,参数2...) -> 参数体表达式 end
定义了匿名函数后,我们直接传入参数 1 和 2,两者相加的结果赋值给 three 变量并打印,也就是结果 3 。
匿名函数可以用赋值给一个变量:
## examples/lambda.avlet add = lambda (x,y) ->x + yend;three = add(1, 2);println(three);
我们将上述匿名函数赋值给了变量 add ,其实相当于定义了一个 add 函数,然后调用 add(1, 2) ,同样返回结果 3 。
从 5.2.4 开始,匿名函数的定义也可以用 fn 语法:
let add = fn(x, y) { x + y};p(add(1, 2));
事实上,上一节通过 fn 定义函数本质上就是类似的步骤,先用 lambda 定义一个匿名函数,然后赋值给函数名指定的变量。
函数是一等公民 (First Class)
在 AviatorScript 中,函数也是一种类型,可以作为参数来传递,可以作为函数的返回值等等。
## examples/function_first_class.avfn square(x) {return x * 2;}let add = lambda(x, y, f) ->f(x) + f(y)end;let add_n = lambda(x) ->lambda(y) ->x + yendend;println(type(square));println(type(add));println(type(add_n));
我们通过 fn 和 lambda 分别定义了三个函数,通过 type 检测他们的类型:
functionfunctionfunction
都是 function 函数类型。
这里的 add 函数接受的第三个参数 f 也是一个函数,我们用它调用 x 和 y,然后再相加,可见函数是可以作为参数来使用:
let s = add(1, 2, square);println(s);
我们将 square 函数作为 f 传入到 add,执行的结果就是 square(1) + square(2) ,结果等于 6:
6
add_n 演示了函数作为返回值的情况, add_n 接受一个参数 x,然后返回一个匿名函数:
lambda(y) ->x + yend
这个匿名函数接受另一个参数 y,他将 x 和 y 相加并返回:
let add_3 = add_n(3);println(type(add_3));println(add_3(1));println(add_3(99));println(add_3(' test'));
我们将 add_n(3) 的返回值赋值给了变量 add_3 ,他的类型是 function,任何时候调用 add_3 函数,它都将和 3 相加,并返回结果:
function41023 test
这个例子也演示了闭包,我们在下一节谈到。
闭包 Closure
AviatorsScript 的函数都支持闭包 closure,函数将捕获当前的上下文环境,哪怕在脱离这个环境的上下文中仍然可以访问到,一个经典的例子,定义一个计数器:
## examples/closure.avlet counter = lambda() ->let c = 0;lambda() ->let result = c;c = c + 1;return result;endend;let c1 = counter();let c2 = counter();println("test c1...");for i in range(0, 10) {x = c1();println(x);}println("test c2...");for i in range(0, 10) {x = c2();println(x);}
counter 是一个函数,它首先初始化了一个局部变量 c,然后返回结果是另一个匿名函数,这个结果匿名函数每次调用返回 c 的值,并递增。
接下来我们调用 counter 两次,赋值给了两个变量 c1 和 c2,分别调用 10 次,输出:
test c1...0123456789test c2...0123456789
可见, c1 和 c2 的值完全独立,分别用于自增计数,互不干扰。
c 在函数 counter 中定义,理论上说局部变量在 counter 函数返回后就“销毁”了,但是匿名的结果函数却“捕获”(closure over)了局部变量 c,哪怕在 counter 返回后,仍然可以继续访问到变量 c ,这就称之为闭包(closure), c 就是所谓自由变量。
闭包模拟 OOP
闭包可以“保存”状态,因此可以用于模拟 OOP:
## examples/closure_oop.av## a function to return a rectangle instance.fn rectangle(x, y) {let r = seq.map("x", x, "y", y);r.area = lambda() ->return r.x * r.y;end;r.circum = lambda() ->return 2 * (r.x + r.y);end;return r;}let s1 = rectangle(3, 4);println("s1 info:");println(s1.x);println(s1.y);println(s1.area());println(s1.circum());let s2 = rectangle(9, 10);println("s2 info:");println(s2.x);println(s2.y);println(s2.area());println(s2.circum());
rectangle 函数用于返回一个 x 和 y 长和宽定义的长方形对象,它有两个方法 area 和 circum 分别用于计算长方形的面积和周长。这里我们用一个 map 来模拟对象,将 x 和 y 作为 key 放入 map,然后定义了两个匿名函数分别赋值给了 r.area 和 r.circum ,这就相当于定义了对象的方法,最后返回结果 map。两个匿名函数是闭包,捕获了自由变量 r。
接下来我们定义了两个长方形 s1 和 s2,并且打印了他们的基本信息。可以看到类似 s1.x 和 s1.area() 这样的调用,接近于面向对象的使用方式。得益于闭包,两个对象的信息是完全独立的:
s1 info:341214s2 info:9109038
甚至,我们可以修改对象的属性:
s2.x = 100;s2.y = 200;println("s2 info after setting:");println(s2.x);println(s2.y);println(s2.area());println(s2.circum());
输出:
s2 info after setting:10020020000600
