函数式编程-2

253 阅读5分钟

函数式编程范式-2.png

1.函数组合的作用是什么?

如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并为一个函数

function compose(f,g){
    return function(x){
        return f(g(x))
    }
}

function first(arr){
    return arr[0]
}

function reverse(arr){
    return arr.reverse
}
//从右到左执行
let last=compose(first,reverse)
console.log(last([1,2,3,4]))

注意:函数组合默认从右到左执行

lodash中组合函数flowRight(),可以组合多个函数

const f=require('lodash')

const toUpper=s=>s.toUppercase()
const reverse=arr=>arr.reverse()
const first=arr=>arr[0]

const fun=f.flowRight(toUpper,first,reverse)
console.log(['one','two','three'])

2.如何模拟实现lodash的flowRight方法

//多函数组合
function compose(...fns){
	return function(value){
		return fns.reverse().reduce(funciton(acc,fn)=>{
            return fn(acc)
		},value)
	}
}
//ES6
const compose=(...fns)=>value=>fns.reverse.reduce((acc,fn)=>fn(arr),value)

3.函数的组合要满足什么?

满足结合律,既可以把g和h组合,也可以把f和g组合,结果一样

4.如何调试组合函数

const f=_.flowRight(_.toUpper,_.first,_.reverse)
console.log(f(['one','two','three']))

const _=require('lodash')

const trace=_.curry((tag,v)=>{
	console.log(tag,v)
	return v
})

const split=_.curry((sep,str)=>_.split(str,sep))
const join=_.curry((sep,array)=>_.join(array,sep))
const map=_.curry((fn,array)=>_.map(array,fn))

const f=_.flowRight(join('-'),trace('map之后'),map(_.toLower),
trace('split之后'),split(' '))
console.log('NEVER SAY DIE')

5.Lodash的fp模块

提供了自动柯里化,函数优先,数据滞后

const fp=require('lodash/fp')

const f=fp.flowRight(fp.join('-'),fp.map(fp.toLower),fp.split(' '))

console.log(f('NEVER SAY DIE'));

6.Lodash-map方法的小问题

// const _=require('lodash')

// console.log(_.map(['23','8','10'],parseInt)); //[ 23, NaN, 2 ]

// parseInt() 

const fp=require('lodash/fp')

console.log(fp.map(parseInt,['23','8','10'])); //[ 23, 8, 10 ]

7.Point Free

Point Free的特点:

  • 不需要指明处理的数据
  • 只需要合成运算过程
  • 需要定义一些辅助的基本运算
//案例-1
const fp=require('lodash/fp')

const f=fp.flowRight(fp.replace(/\s+/g,'_'),fp.toLower)

console.log(f('hello   World'))

//案例-2

const fp=require('lodash/fp')

// const firstLetterToUpper=fp.flowRight(fp.join('. '),fp.map(fp.first),fp.map(fp.toUpper),fp.split(' '))
const firstLetterToUpper=fp.flowRight(fp.join('. '),fp.map(fp.flowRight(fp.first,fp.toUpper)),fp.split(' '))

console.log(firstLetterToUpper('world wild web')) //W. W. W

8.为什么要学函子?

在编程式函数中如何把副作用控制在可控的范围内异常处理异步操作

9.什么是函子

容器:包含值和值的变形关系(这个变形关系就是函数)

函子: 是一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)

// 一个容器,包裹一个值
class Container {
    // of 静态方法,可以省略 new 关键字创建对象
    static of (value) {
        return new Container(value)
    }
    constructor (value) {
        this._value = value
    }
    // map 方法,传入变形关系,将容器里的每一个值映射到另一个容器
    map (fn) {
        return Container.of(fn(this._value))
    }
}
// 测试
Container.of(3)
.map(x => x + 2)
.map(x => x * x)

总结:

  • 函数式编程的运算不直接操作值,而是由函子完成
  • 函子就是一个实现了map契约的对象
  • 我们可以把函子想象成一个盒子,这个盒子里封装了一个值
  • 想要处理盒子中的值,我们需要给盒子的map方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理
  • 最终map方法返回一个包含新值的盒子(函子)

10.Maybe函子

作用:对外部的空值(null和undefined)进行处理

class MayBe{
    static of(value){
        return new MayBe(value)
    }
    constructor (value){
        this._value=value
    }
    map(fn){
        return this.isNothing()?MayBe.of(null):MayBe.of(fn(this._value))
    }

    isNothing(){
        return this._value===null||this._value===undefined
    }
}

// let r=MayBe.of('Hello world')
//     .map(x=>x.toUpperCase())

//     console.log(r);
let r=MayBe.of(null)
.map(x=>x.toUpperCase())

console.log(r);//MayBe { _value: null }

11.Either函子如何实现?作用是什么?

作用:异常会让函数变得不纯,它可以用来做异常处理


class Left{
    static of(value){
        return new Left(value)
    }
    constructor(value){
        this._value=value
    }
    map(fn){
        return this
    }
}

class Right{
    static of(value){
        return new Right(value)
    }
    constructor(value){
        this._value=value
    }
    map(fn){
        return Right.of(fn(this._value))
    }
}

// let r1=Right.of(12).map(x=>x+2)
// let r2=Left.of(12).map(x=>x+2)
// console.log(r1);
// console.log(r2);

function parseJSON(str){
    try {
        return Right.of(JSON.parse(str))
    } catch (error) {
        return Left.of({error:error.message})
    }
}

// let r=parseJSON('{name:zs}')
// console.log(r); //Left { _value: { error: 'Unexpected token n in JSON at position 1' } }
let r=parseJSON('{"name":"zs"}')
    .map(x=>x.name.toUpperCase())
console.log(r); //Right { _value: 'ZS' }

12.IO函子内部做了那些操作?

IO函子中的_value是一个函数,这里把函数作为值来处理

IO函子把不纯的动作储存在_value中,延迟执行这个不纯的操作(惰性行为),保证当前的操作纯

把不纯的操作交给调用者来处理


const fp=require('lodash/fp')

class IO{
    static of(value){
        return new IO(function(){
            return value
        })
    }
    constructor (fn){
        this._value=fn
    }
    map(fn){
        return new IO(fp.flowRight(fn,this._value))
    }
}

// 调用
let r=IO.of(process).map(p=>p.execPath)
console.log(r) //IO { _value: [Function (anonymous)] }
console.log(r._value()) //C:\Program Files\nodejs\node.exe

13.folkfale如何使用?

folkfale是一个标准的函数式编程库,只提供一些函数式处理的操作

//folktale中的compose、curry
const {compose,curry}=require('folktale/core/lambda')
const {toUpper,first}=require('lodash')

let f=curry(3,(x,y,z)=>{
    return x+y+z
})
console.log(f(1,2,3)); //6
console.log(f(1)(2)(3)); //6

let f=compose(toUpper,first)
console.log(f(['one','two']));//ONE

14.Task异步执行

folktale中的Task用来实现异步任务


//Task 处理异步任务  
const fs=require('fs')
const {task}=require('folktale/concurrency/task') //当前使用的是folktale(2.3.2)
const {split,find} =require('lodash/fp')

function readFile(filename){
    return task(resolver=>{
        fs.readFile(filename,'utf-8',(err,data)=>{
            if(err)  resolver.reject(err)
            resolver.resolve(data)
        })
    })
}

readFile('package.json')
    .map(split('\n'))
    .map(find(x=>x.includes('version')))
    .run()
    .listen({
        onRejected:err=>{
            console.log(err); 
        },
        onResolved:value=>{
            console.log(value);  //"version": "1.0.0",
        }
    })

15.Pointed函子的作用?如何使用?

pointed函子是实现了of静态方法的函子

of方法是为了避免使用new来创建对象,更深层次的是of方法用来把值放到上下文Context中(把值放到容器中,使用map来处理值)

class Container{
    static of(value){
        return new Container(value)
    }
    ......
}
    Container.of(2)
	.map(x=>x+5)

16.Monad(单子)

  • monad函子是可以变扁的Pointed函子,IO(IO(x))
  • 一个函数子如何具有join和of两个方法并遵守一些定律就是一个Monad
//使用IO时(会出现函子嵌套问题)
const fs=require('fs')
const fp=require('lodash/fp')

class IO{
    static of(value){
        return new IO(function(){
            return value
        })
    }
    constructor (fn){
        this._value=fn
    }
    map(fn){
        return new IO(fp.flowRight(fn,this._value))
    }
}

let readFile=function(filename){
    return new IO(function(){
        return fs.readFileSync(filename,'utf-8')
    })
}

let print=function(x){
    return new IO(function(){
        console.log(x);
        return x
    })
}

let cat=fp.flowRight(print,readFile)
//IO(IO(x))
let r=cat('package.json')._value()._value()
console.log(r);//package.json文件内容

相关链接:

函数式编程指北

函数式编程入门-阮一峰

Pointfree编程风格指南

图解Monad