0%

通过组合lambda动态创建函数

继前几天写了个程序化打表的网页小工具之后,一方面我觉得手动输入表达式太麻烦了,可以用下拉列表来组合表达式,另一方面就是不使用字符串转函数,而是用lambda来组合创建出新的函数,这种方法适用于所有可以创建函数对象的编程语言。

基本原理

主要的流程就是递归的解析表达式,如果当前解析出来的是一个函数,那就依次解析它的每个参数,每个参数解析出来也会是一个函数对象,递归的终止条件为不需要参数的函数。

我也还没有深入研究如何引入参数,所以返回的全是无参的函数,传递参数的方法是通过类似getX()这样的函数来获取外部参数,同时也作为递归的终止条件。

代码实现

因为纯C++的人机交互方式其实挺少的,所以还是用字符串来举例子,先写一个很简陋的parser,能够将一个字符串解析为函数名和各个参数的表达式,第0个元素为函数名,后续依次为各个参数字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
vector<string>parser(string str){
vector<string>res;
int start=0,i=0,level=0;
for(;i!=str.size();++i){
switch(str[i]){
case '(':
if(level==0){
res.emplace_back(str.substr(start,i-start));
start=i+1;
}
++level;
break;
case ')':
--level;
if(level==0){
res.emplace_back(str.substr(start,i-start));
start=i+1;
}
break;
case ',':
if(level==1){
res.emplace_back(str.substr(start,i-start));
start=i+1;
}
break;
}
}
if(i!=start)res.emplace_back(str.substr(start,i-start));
return res;
}

字符串处理的函数写起来就是这么麻烦,这个不是重点,就不过多解释了,下面才是主要的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int x=0,y=1;

int f(int a){return a+1;}
int g(int a,int b){return a+b;}
int getx(){return x;}
int gety(){return y;}

function<int()> analysis(string str){
auto args=parser(str);
if(args[0]=="x")return getx;
if(args[0]=="y")return gety;
if(args[0]=="f")return [arg1=analysis(args[1])]{return f(arg1());};
if(args[0]=="g")return [arg1=analysis(args[1]),arg2=analysis(args[2])]{return g(arg1(),arg2());};
return nullptr;
}

上面的例子中,analysis将一个表达式字符串解析为一个函数,对于嵌套的函数则对其每个参数递归的解析为函数,再将函数调用作为上一层函数的参数,例如f(g(x,y)),先解析出函数名f,再对它的参数字符串g(x,y)调用analysis,函数g有两个参数,先对x调用analysis,达到终止条件,返回getx,同理y返回gety,这时候g的参数全部解析出来了,返回一个函数对象,即g(getx(),gety()),作为f的参数,最终返回的函数效果上等价于

1
auto foo(){return f(g(getx(),gety()));}

最后可以写个main来试试

1
2
3
4
int main(){
cout<<analysis("g(f(x),y)")()<<endl;
return 0;
}

不出意外上面的运行结果会是2。

最后

运行效率上,因为套了很多层lambda,运行效率肯定会慢,或许也像是某种jit(即时编译),只能说是一个很有趣的玩具。后续应该可以有两种改进,一种是用数组传参,然后把所有函数都放在一个map里,做一个通用化的组合。另一种是用class把变量包装起来,然后再加上模板或许可以做出像真正的函数一样传参调用函数对象(现在的这种形式不能传参,等价的方法是在外部修改x和y)