函数式编程

谈谈函数式编程在js的实践。

函数式编程

是什么

首先讲讲编程范式,所谓编程范式就是代码世界的方法论。大家比较熟的就是面向过程和面向对象了,这些范式并不是锁定语言的,但是有些却是需要语言特性支持。比如c就是常见的面向过程的编程范式语言,但是因为指针、结构体的存在让它也具有一定的面向对象的能力。java是常见的面向对象的语言,但是要是写类和方法时没有封装,继承的思想,那也不过是面向过程包上了一层class的外衣。

以上解释了范式不限定语言,那何为范式需要语言支持呢?这就提到函数式编程了,函数式编程范式的思想是把一切行为都用函数连接起来,每个函数都有确定的输出,整体就是一个y=f(x)的形式,扩展下就有y=f(g(t(x)))这样一层一层嵌套的函数调用。写的优雅一点就是gg=g(t),ff=f(g),y=ff(x),可以看到这里把函数t作为函数g的参数了,然后组合出了新的函数gg,这里可以引出函数式编程的要求是具有吧函数作为参数传递的能力,官方点说法就是“函数是一等公民”。自然,这个也不是必要条件,只是个充分条件(函数是一等公民一定可以函数式,函数式不一定要函数是一等公民,但是需要多绕几个弯比如C++可以重载类的函数调用操作符就可以创造函数对象这种东西就可以做参数传递了)另外,函数式是一种思想,我们学习只是为了提高某些代码的简洁性和优雅性,不能为了函数式而函数式编程。

为什么

扯了那么多,那函数式编程有啥用呢?为了提高某些代码的简洁性和优雅性。。。废话,具体点?传统编程中会有需要状态管理以及处理中间变量的情况,比如求和,那就需要一个sum变量,然后手动遍历数组每个数字并加到sum里面。恩很轻松就搞定了。那需要正负分开求和呢?那就加个if。。。需要求平方和呢?当然这个例子有点钻牛角尖,其实我想说明的是,随着需求的变多我们需要申明更多的变量来存储这个结果,一个大型的系统,变量越来越多状态也会更加复杂,就会趋于不可控,于是就出来很多状态管理机制比如有限状态机之类的。但是函数式的思想就是记录过程不记录结果,关注点从每一步做法转移到每个过程的拼接。好处就是定义一些基本函数后,有新的需求就把基础函数组装起来就可以了,比如arr.sum(), arr.bigthan0().sum(), arr.smallthan0().sum()

上面只是简单提了状态多带来的副作用(不好管理),函数式编程真正的优势是没有中间状态就可以不用担心状态被修改或者出现竞争导致的多线程下各种复杂问题,所以可以轻松应付高并发环境。

怎么做

函数式的一些基本概念

  1. 函数没有副作用,就是所谓的纯函数。即函数对同一输入有相同输出,且不会修改外界的状态。
  2. 函数可以作为参数传入别的函数并在需要的时候调用,也可以从函数返回新的函数。就是所谓的高阶函数。
  3. 多个参数的函数可以进行参数拆解,返回新的函数(需要比较少的参数)就是所谓的科里化。科里化可以更好地函数复用。

js中函数是一等公民,它是天生的支持函数式编程的语言,es6中箭头函数的引入又在写法上带来了便捷性(箭头函数还有更深的特性比如不产生this,只不过与这个主题无关),另外箭头函数的写法各个语言都有一定的支持(就是大名鼎鼎的lambda表达式)。然后js的Array对象有很多支持函数式的内置函数也给函数式编程带了很多便利,比如map,filter,reduce等。

比如花样求和

1
2
3
4
const arr = [1,4,9,5,2,6,-2,-3,-7];
arr.reduce((sum,item)=>sum+item); //求和
arr.filter(x => x>0).reduce((sum, x) => sum + x); //正数求和
arr.map(x => x*x).reduce((sum, x) => sum + x); //平方和

可以看到就是map,reduce,filter的简单组合

1
2
3
const sum = (a,b) => a+b;
const fun = (f,a,b) => f(a,b)
fun(sum,1,2) //3

进一步学习

函数式编程入门教程
如何读懂并写出装逼的函数式代码(看看长长见识就好了,千万别写出这种代码…)