1.什么是函数式编程及其好处

函数式编程是一种编码思想,是一种通过编写纯函数、无副作用、不改变外部状态的一种编码构建方式。

函数式编程是声明式的不是命令式的。声明式编程是将程序的描述和求值分离开来的。更多的是关注于表达式的程序逻辑并将控制流交给其他部分去处理。

函数式编程的火热和受追捧是其具有易扩展性、易重构性、易测试性、复用性强等优点。

在刚接触函数式编程时被其多个术语吓到,脑袋里完全是“这是在说啥?彷如我是一个智障...”,但多阅读和仔细看几遍例子会发现平时还是有接触过,只不过现在都披了一层外衣。

2. 函数式编程的基本名词解释

2.1 纯函数

即一个相同的输入只有相同的输出,并且不会产生副作用。
相反不纯的函数在相同的输入是会出现不同的结果的。

    const array = [1, 2, 3]
    // 纯函数
    array.slice(0, 3) // [1, 2, 3]
    array.slice(0, 3) // [1, 2, 3]
    // 不纯的函数
    array.splice(0, 3) // [1, 2, 3]
    array.splice(0, 3) // []

2.2 副作用

副作用就是函数在运用的过程中改变外部变量的状态或与函数外的状态进行交互产生的。可能会随着外部的状态改变影响本身的函数。我们在编程中应尽量使用无副作用的函数,从而保证函数的纯度。

2.3 引用透明

引用透明是指函数中的变量都是来源于函数参数的,不会引入任何非参数形式的变量。这样可以明确每次数据的来源,减少函数的副作用。

2.4 函数是"一等公民"

是指函数跟其他变量一样可以是一个函数的参数、函数的返回值、对某个变量赋值等操作。
另外这个"一等公民的特性"我们经常用到。比如:将函数赋值给一个变量、异步的回调函数、闭包返回的函数等

2.5 函数的柯里化

咋一眼看“柯里化”这个词,还以为是把函数分成多个的小小的细细的的小函数进行复用呢。其真正意义简单的说就是在函数调用时如果只传部分参数则返回一个可接收剩余参数的函数,待所有参数全部传完返回最终结果。

    // 如果未传递所有参数,返回一个函数,等待剩余的参数。
    const curry = fn => firstArg => secondArg => fn(firstArg,secondArg)
    const add = (a,b)=>a+b;
    const sum = curry(add)
    sum(1000)(24) 
    //1024

柯里化是通过拆分函数参数将函数变为了高阶函数的过程。心想那要是有n个参数难道要一层层的套进去吗?偶买噶,这比剥洋葱还辛苦...直到看到某个例子时,骤然感到”too young too simple“(详见3.1)

2.6 高阶函数

 作为其他函数的参数或别其他函数返回的函数。例如map、filter、reduce、sort都是高阶函数。并且这些高阶函数在对集合进行操作只用关心具体求值逻辑,map等函数默默的处理了控制流的部分。
const arr = [1,2,3,4,5]
//将每个元素变为现在的10倍
//最早无map操作
const result = []
for(let i=0;i<arr.length;i++){
    result.push(arr[i]*10)
}
//map操作 无需关注控制流,只写具体逻辑即可
const mapResult = arr.map(item=>item*10)

3. 函数式编程的核心内容

3.1 函数的”柯里化“

由于javascript的特殊性在函数f(a,b,c)调用时,仅传a的值剩余的参数会被设为undefined。而柯里化函数,它要求所有参数都有明确的定义,当使用部分参数调用时,会返回一个新的函数接收剩余参数,当剩余参数被提供后调用返回最终结果。
在实际开发中,有很多函数都不是柯里化的,可以使用函数转化,也可以用loash的curry函数

/* 当未传全部参数,返回接收剩余参数的函数。
* 参数全部传入,执行函数
* 这里最神奇的是 args里面记录的是全部传入的参数。
* 是通过闭包特殊性将args的一次次传入的变量存储在内存
*/
const curry = (fn)=>{
    const arity = fn.length
    return function $curry(...args){
        if(args.length < arity){
            return $curry.bind(null, ...args)
        }
        return fn.call(null, ...args)
    }
}
const curryJoin = curry((tag, str)=>str.join(tag))
const joinCom = curryJoin('-') // 返回一个函数
joinCom(["h", "e", "l", "l", "o"]) // "h-e-l-l-o"

3.2 函数组合

将已经分解的简单函数组合成复杂行为的过程。也就是说有函数f(x),g(x)。通过组合变成f(g(x))的过程。类似于f(x)*g(x)=>f(g(x)),相当于编程中g(x)的结果作为f(x)的参数。
肯定有人想既然这样,为啥还有组合,我直接把函数当参数传递不就好了,这样做实现功能当然没问题。但不易于维护呀,如果我要重构f(x)不是还得看g(x)这个函数来判断参数来源逻辑?使得重构流程变长难以维护。函数组合的函数优点就在于描述和求值分开,调用方基本只用处理求值函数逻辑,不需要掌握全局,使维护变得简单。

const compose = (f,g)=> x => f(g(x))
const split = curry((tag,x)=> x.split(tag))
const reverse = x=> x.reverse()
const join = curry((tag,x)=> x.join(tag))
const composeName = compose(reverse, split('-'))
beginCompose('cherry-zhang') // ['zhang','cherry']

const replaceComponse = compose(join(' love '),split('-'))
replaceComponse('l-china') // "l love china"

3.3 函子(Functor)

书上说函子是用来将两个范畴关联起来的。感觉这个描述很抽象,我理解的就是在初始化时给容器添加值,暴露map方法让外部函数可以操控容器内的值,并返回改变后的值创建的新容器。

3.3.1 Container

定义一个Container,约定of方法返回新创建的Container。并暴露一个map方法使得外部函数可以操作容器中的值并返回Container对象,这样能连续调用map进行多次操作。

//下面来操作一下容器的值
const Container = x=> this._value = x  
Container.of = x=> new Container(x)
Container.prototype.map = f=> Container.of(f(this._value))
    
Container.of(19).map(x=> x-1)// Container(18)
Container.of('cherry').map(concat(' very nice'))//Container('cherry very nice')
3.3.2 Maybe

Container在传入null或undefined可能出错,而在日常工作中判断输入是否为空是常见操作。Maybe与Container不同之处在于调用函数前会先检查是否为空再进行处理。

const Maybe = x=> this._value = x
Maybe.of = x => new Maybe(x)
Maybe.prototype.isNoting = ()=> this._value === null || this._value === undefined
Maybe.prototype.map = f => this.isNoting?Maybe.of(null):Maybe.of(f(this._value))
Maybe.of(18).map(x=>x+1) //Maybe(19)
Maybe.of(null).map(x=>x+1) //Maybe(null)

其实还有一些异常处理,异步回调的处理的函子这里就不再赘述。通过Container、Maybe的例子感觉蛮像函数对象的,类似创建一个函数对象,调用函数对象的某个函数属性且这个函数的参数是自定义的函数。这样也是能完成Container,Mayby暴露map操作内部值的逻辑。所以没有感觉到函子有什么特别的好处,可能这是函数编程思想的体现吧。

4. 总结

通过这段时间看书和博客,感觉函数式编程无论是将描述和求值分开、将函数尽可能的变纯、减少副作用、引用透明等等无法就是想说求值时将控制流交出去只关注求值具体实现逻辑、不要对外部参数进行读写、一切凡是变量的部分统统采取函数参数的形式传递。这样做的好处是每个函数都是独立的,想要重构和测试时仅关注当前的纯函数逻辑不用关注整个代码逻辑的实现。因为纯函数一个相同的输入只有相同的输出。
另外现在有很多函数式编程的库,可以站在巨人的肩膀上看世界哟。Lodash.js,underscore.js


cherry
15 声望1 粉丝

佛系的打码中...