总结下函数式编程与命令行编程体感上的最大区别:
(2022-05-22 09:43:46)总结下函数式编程与命令行编程体感上的最大区别:
- 函数是一等公式,我们应该熟悉变量中保存函数再对其进行调用
- 函数可以出现在返回值里,最重要的用法就是把输入是n(n>2)个参数的函数转换成n个1个参数的串联调用,这就是传说中的柯里化。这种减少了参数的新函数,我们称之为偏函数
- 函数可以用做函数的参数,这样的函数称为高阶函数
偏函数可以当作是更灵活的参数默认值。
比如我们有个结构叫spm,由spm_a和spm_b组成。但是一个模块中spm_a是固定的,大部分时候只需要指定spm_b就可以了,我们就可以写一个偏函数:
const getSpm = function(spm_a, spm_b){ return [spm_a, spm_b];}const getSpmb = function(spm_b){ return getSpm(1000, spm_b);}console.log(getSpmb(1007));
高阶函数我们在前面的map和flatMap里面已经用得很熟了。但是,其实高阶函数值得学习的设计模式还不少。
比如给大家出一个思考题,如何用函数式方法实现一个只执行一次有效的函数?
不要用全局变量啊,那不是函数式思维,我们要用闭包。
once是一个高阶函数,返回值是一个函数,如果done是false,则将done设为true,然后执行fn。done是在返回函数的同一层,所以会被闭包记忆获取到:
const once = (fn) => { let done = false; return function() { return done ? undefined : ((done=true), fn.apply(this,arguments)); }}let init_data = once( () => { console.log("Initialize data"); });init_data();init_data();
我们可以看到,第二次调用init_data()没有发生任何事情。
递归与记忆
前面介绍了这么多,但是函数编程其实还蛮复杂的,比如说涉及到递归。
递归中最简单的就是阶乘了吧:
let factorial = (n) => { if (n===0){ return 1; } return n*factorial(n-1);}console.log(factorial(10));
但是我们都知道,这样做效率很低,会重复计算好多次。应该采用动态规划的办法。
那么如何在函数式编程中使用动态规划,换句话说我们如何保存已经计算过的值?
想必经过上一节学习,大家肯定想到要用闭包,没错,我们可以封装一个叫memo的高阶函数来实现这个功能:
const memo = (fn) => { const cache = {}; return (arg) => cache[arg] || (cache[arg] = fn(arg));}
逻辑很简单,返回值是lamdba表达式,它仍然支持闭包,所以我们在其同层定义一个cache,然后如果cache中的某项为空则计算并保存之,如果已经有了就直接使用。
这个高阶函数很好用,阶乘的逻辑不用改,只要放到memo中就好了:
let fastFact = memo( (n) => { if (n<=0){ return 1; }else{ return n * fastFact(n-1); } });
在本文即将结尾的时候,我们再回归到前端,React Hooks里面提供的useMemo,就是这样的记忆机制:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
小结
综上,我们希望大家能记住几点:
- 函数式编程的核心概念很简单,就是将函数存到变量里,用在参数里,用在返回值里
- 在编程时要时刻记住将无副作用与有副作用代码分开
- 函数式编程的原理虽然很简单,但是因为大家习惯了命令式编程,刚开始学习时会有诸多不习惯,用多了就好了
- 函数式编程背后有其数学基础,在学习时可以先不要管它,当成设计模式学习。等将来熟悉之后,还是建议去了解下背后的真正原理