一开始我并没打算在larva实现前面说的解析式和这篇要说的lambda,因为当时觉得解析式可以用循环来代替,lambda则可以用可调用对象代替,但后面发现还是做了比较好,毕竟这俩东西太方便了,如果没有的话代码写起来很啰嗦。其实碰到的第一个问题是列表解析式,因为我本来觉得,对我来说lambda使用的地方不是很多(虽然很多人喜欢用),不过在开始做列表解析式的时候,我忽然想到python中的map函数,如果用map配合lambda也是可以实现解析式的功能,所以先实现的反而是lambda,然而在做到一半的时候发现,预先设计的lambda语法配合map只能实现...for...in...的解析式,不能实现...for...in...if...,除非给map函数增加参数,所以又回过头做列表解析式,于是最后的结果是,列表、字典解析式和lambda都做了
语法方面,larva的lambda和python一样,不过在机制上做了一点修改。python的lambda是一个返回一个表达式值的匿名函数,和def没有什么区别,即:
f = lambda x : x + 1
相当于
def f(x):
return x + 1
python中一切都是对象,所以这里的f也就是个普通变量,引用了一个函数对象,所以如果不是匿名地使用函数,用def代替lambda可读性可能还好些,这种机制虽然灵活,但有点太灵活了,如果忘了写函数的括号,语法上没错但是是另一个含义,比如我经常看到有人在关闭文件的时候写f.close,实际只是对close方法对象求值,没有实际关闭。考虑到这个问题,以及实际中函数本身作为对象的情况相对直接调用的情况少很多,larva规定函数、类、方法等不是对象,也就是不能作为单独表达式存在,如果要像一个对象一样用,就需要用lambda封装,比如:
g = lambda x : f(x)
则调用g(x)就相当于f(x),只不过g是对象,而f不是,我觉得显式的语法更方便突出函数的非调用使用
lambda在两种语言中的重要区别可以用一个例子体现:
def main():
i = 123
f = lambda : i
for i in range(10):
print f()
python中执行这段代码,会打印0到9,原因在于,这个lambda是一个闭包函数,它里面用到的这个i是引用main函数当前调用的局部变量环境,因此main函数中的i变化时,调用f返回的是main中i的值
而类似的代码在larva下会打印出10个123,原因在于larva在生成lambda对象的时候不是关联到当前函数局部环境,而是对当前函数局部环境做了一个快照,即拷贝了一份保存到自己对象内部,如果要实现上述python的效果,可以改成这样:
func main():
i = [123]
f = lambda : i[0]
for i[0] in range(10):
print f()
由于lambda保存局部环境快照时用的浅拷贝,所以用一个容器就做到了
之所以这么做,起源于之前在网上看到一些朋友关于这个问题的争议,究竟是关联环境好,还是拷贝环境好,似乎更多人觉得拷贝更直观一些,不过我没有研究其他语言中的lambda或闭包是怎么处理的
lambda的实现和前述的列表、字典解析式类似,对于代码中每个lambda,都对应一个类,这个类有内建的op_call方法,也就是函数调用运算“()”的重载实现,当然这个类是继承自LarObj的,所以:
f = lambda x : x + i
就被实现为类似java代码:
f = new Lambda_0001(i);
其中当前环境,也就是变量i的值在构造时传入,由lambda对象保存,这个lambda对象的实现类似如下代码:
class Lambda_0001 extends LarObj
{
private LarObj m_i;
Lambda_0001(LarObj i)
{
m_i = i;
}
public LarObj op_call(LarObj x)
{
return x.op_add(m_i);
}
}
而如果lambda中出现全局变量和this/super等,处理方式也和列表解析式的实现一致,全局变量不做快照,按照每次调用时的实际值参与运算,而若在方法中使用lambda,将Lambda_XXXX类实现在对应外部类中,this/super的访问等于是java帮忙做了个快照了