从闭包谈函数式编程
记得开始学习golang的时候,boss给我发了一篇paper,讲的是函数式编程,那个时候看了一遍,懵懵懂懂,只有一个感觉,函数式编程怎么好像讲的都是数学公式(说句实话,我那个时候还真不理解boss为什么给我发那篇paper,只是想反正多看点东西总不会错)?最近一段时间,go用的比较多,业务逻辑让我思考了很多问题,虽然go并非函数式语言,但却支持了很多函数式的概念,让编程变得简单。当然,在写代码的过程中,我就很怀疑,使用了函数式编程会不会导致运行效率变低?
这篇文章就从几方面学习函数式编程(by 魏加恩)
1. 数学公式和函数式编程有什么关系
2. 函数式编程有什么特点,go支持了哪些概念
3. 函数式编程与运行效率
1. 数学公式和函数式编程有什么关系
举个简单例子吧,数学中有一个概念叫做映射(y=f(x)),说的通俗一点就是函数啦。而最熟悉的应该就是二次函数(抛物线y=a*x*x+b*x+c)
现在coding实现求抛物线上某一点的值,我们知道a,b,c是参数,x是自变量,y是因变量,如果是以前,我也许会这么实现(为了纪念我许久未写的c++,还是用c++来写一下)
double getParabola(double a,double b,double c,double x) { return a*x*x+b*x+c; }
好了,问题来了,
问题一、给定抛物线,求x=2,x=3,x=4时的值,就是下面的做法了
resultA = getParabola(a,b,c,2) resultB = getParabola(a,b,c,2) resultC = getParabola(a,b,c,2)
这在程序中,是很正常的做法。但是,如果从数学的角度来看,有没有办法变得符合数学公式思维呢?以下是我的另外一种实现(这里用go来实现哈,因为c++我知道怎么写),
func getParabola(aa,bb,cc float32){ var a = aa var b = bb var c = cc a := func(x float32) { return a*x*x+b*x+c } return a }
然后,同样是对于问题一,解决方案如下
parabola := getParabola(a,b,c) resultA := parabola(2) resultB := parabola(3) resultC := parabola(4)
是不是跟求函数值一样?所以,数学关系在函数式编程中得到了很好的体现。
2. 函数式编程有什么特点,go支持了哪些概念
函数式编程有三大特性
1. 变量的不可变性: 变量一经赋值不可改变。如果需要改变,则必须复制出去,然后修改。 go中,string变量一经赋值,不可以像c++那样,c[2]='a'这样的修改,而是要显式转化为[]byte,然后进行修改。但是已经是另外一块内存了。
2. 函数式一等公民: 函数也是变量,可以作为参数,返回值等在程序中进行传递。 这个特性,c++和go应该都是支持的。
3. 尾递归:递归的概念在斐波那契数列的时候,就学习过了。如果递归很深的话,堆栈可能会爆掉,并导致性能大幅度下降。而尾递归优化技术,编译器如果支持的话,可以在每次递归时重用stack(尾递归表示递归调用发生在最后一步,这个时候,之前的结果都作为参数传递给最后一步的调用,所以之前的状态就没有任何作用了,所以可以重用stack)。 go是否支持,这个我暂时不清楚。
函数式编程常用技术
1. map&reduce&filter
map用于对每一个输入,调用同一个函数,产生一个输出,比如c++中的for_each,hadoop里面的map,python的map等。
reduce用于对每一个输入,加上上一个输出,得到下一个输出,比如python和hadoop中的reduce,
filter用于做过滤,比如c++的count_if等。
2. 递归
3. pipeline
把函数实例放到一个数组或是列表中,然后把数据传给这个action list,输入顺序地被各个函数所操作(意思是每一个函数的输出,作为另外一个函数的输入,数据是流动的,计算是固定的,类似storm的概念),最终得到我们想要的结果。
4. 其他(有待进一步学习)
3. 函数式编程与运行效率
函数式编程最重要的一个概念就是函数式一等公民,函数跟变量是一样的。可以作为参数,返回值等。不赞成使用赋值语句,所以比较多的使用递归,所以函数式编程效率肯定会比较低。
最近我用的比较多的是闭包,闭包的概念就是一个环境(一个或多个变量)加上一个函数,每一次对闭包表达式求值,都得到一个隔离的结果,这跟普通函数是不一样的,普通函数就是一段可执行的代码,只要入口确定了,调用的位置也就确定了。举个例子,上面抛物线的例子,调用
a:=getParabola(0.2,0.1,0.3) b:=getParabola(0.1,0.1,0.4)
得到的是两条抛物线。之所以我觉得效率会降低,是因为闭包本身就是一个求值赋值的过程,涉及到变量的创建销毁。当然,我没有实际去测试性能。如果后续发行server效率降低,也许这是一个需要考虑的地方。
这篇文章只是最近一段时间写代码的一个感性认知加上网上查找一些资料。作为一个备忘,后续可以深入学习。