匿名函数 lambda
你可以通过 lambda
语法定义一个匿名函数:
## examples/lambda.av
let 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.av
let add = lambda (x,y) ->
x + y
end;
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.av
fn square(x) {
return x * 2;
}
let add = lambda(x, y, f) ->
f(x) + f(y)
end;
let add_n = lambda(x) ->
lambda(y) ->
x + y
end
end;
println(type(square));
println(type(add));
println(type(add_n));
我们通过 fn 和 lambda 分别定义了三个函数,通过 type 检测他们的类型:
function
function
function
都是 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 + y
end
这个匿名函数接受另一个参数 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 相加,并返回结果:
function
4
102
3 test
这个例子也演示了闭包,我们在下一节谈到。
闭包 Closure
AviatorsScript 的函数都支持闭包 closure,函数将捕获当前的上下文环境,哪怕在脱离这个环境的上下文中仍然可以访问到,一个经典的例子,定义一个计数器:
## examples/closure.av
let counter = lambda() ->
let c = 0;
lambda() ->
let result = c;
c = c + 1;
return result;
end
end;
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...
0
1
2
3
4
5
6
7
8
9
test c2...
0
1
2
3
4
5
6
7
8
9
可见, 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:
3
4
12
14
s2 info:
9
10
90
38
甚至,我们可以修改对象的属性:
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:
100
200
20000
600