函数式编程—4—纯函数(函子)

Point Free (编程风格)

将数据处理的过程定义成与数据无关的合成运算,不需要代表数据的那个参数,主要吧简单的运算步骤合成到一起,在使用这种模式之前,我们需要定义一些辅助的基本运算函数

  • 不需要指明处理的数据
  • 只需要合成运算过程
  • 需要定义一些辅助的基本运算函数
const fp = require('loadsh/fp')
const f = fp.flowRight(fp.join('-'),fp.map(_.toLower),fp.split(' ')) 

简单演示

HELLO WORD ==> hello_word

普通方式

function func1(str){
    return str.toLowerCase().replace(/\s+/g,'_')
}
console.log(func1('HELLO WORD')) // hello_word

Point Free

const fp = require('loadsh/fp')
const func2 = fp.flowRight(fp.replace(/\s+/g,'_'),fp.toLower)
console.log(func2('HELLO WORD')) // hello_word

案例演示

word wild web ==> W.W.W

const fp = require('loadsh/fp')
const firstLetterToUpper = fp.flowRight(fp.join('.'),fp.map(fp.flowRight(fp.toUpper,fp.first)),fp.split(' '))
console.log(firstLetterToUpper('word wild web')) // W.W.W

Functor (函子)

为什么需要学习函子

把副作用控制在可控的范围内以及异常处理、异步操作等

什么是Functor

  • 容器 包含值和值变形的关系
  • 函子 一个特殊的容器,通过普通对象直线,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)
  • 实现链式调用
class Container {
    constructor(value){
        this._value = value
    }
    map(fn){
        return Container.of(fn(this._value))
    }
    static of(value){
        return new Container(value)
    }
}

let r = Container.of(1).map(x=>x+1).map(x=>x*x)
console.log(r) // 4

函子总结

  • 函数式编程的运算不字节操作值,而是使用函完成
  • 函子就是实现了map契约的对象
  • 我们可以把函子想象成一个盒子,这个盒子里封装了一个值
  • 想要处理盒子中的值,我们需要给盒子的map方法传递一个处理值的函数(纯函数),这个函数来对值进行处理
  • 最终map方法返回了一个包含新值的盒子(函子)
    关于副作用异常的出现方式
class Container {
    constructor(value){
        this._value = value
    }
    map(fn){
        return Container.of(fn(this._value))
    }
    static of(value){
        return new Container(value)
    }
}

Container.of(null).map(x=>x.toUpperCase()) //报错
// 输入为null 会触发副总用

MayBe (函子)

  • 对错误进行相应的处理
  • 可以对外部空值的情况做处理(空值副作用在允许的范围)
class Mybe {
    static of (value) {
        return new Mybe(value)
    }
    constructor(value) {
        this._value = value

    }
    map(fn) {
        return this.isNothing() ? Mybe.of(null) : Mybe.of(fn(this._value))
    }
    isNothing() {
        return this, this._value === null || this._value === undefined
    }

}
const r1 = Mybe.of('hello word').map(x=>x.toUpperCase()) // null
const r2 = Mybe.of(null).map(x=>x.toUpperCase()) // null
console.log(r1) // Mybe { _value: 'HELLO WORD' }
console.log(r2) // Mybe { _value: null } 

MyBe 函子问题 多次出现null后无法确定 null出现的位置

class Mybe {
    static of (value) {
        return new Mybe(value)
    }
    constructor(value) {
        this._value = value

    }
    map(fn) {
        return this.isNothing() ? Mybe.of(null) : Mybe.of(fn(this._value))
    }
    isNothing() {
        return this, this._value === null || this._value === undefined
    }
}
const r1 = Mybe.of('hello word')
            .map(x=>x.toUpperCase())
            .map(x=>null)
            .map(x=>x.split(' '))
            .map(x=>null)
            .map(x=>x.split(' '))
console.log(r1) // Mybe { _value: null } 

EitHer (函子)

  • EitHer两者中的任何一个,类似于if…else …的逻辑
  • 异常会让函数变得不纯,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))
    }

}

function parseJosn(str){
    try {
        return Right.of(JSON.parse(str))
    } catch (e) {
        return Left.of({err:e.message})
    }
}

const r = parseJosn('{ name zs }').map(x=>x.name.toUpperCase())
console.log(r) // Left { _value: { err: 'Unexpected token n in JSON at position 2' } }

const r2 = parseJosn('{ "name":"zs"}').map(x=>x.name.toUpperCase())
console.log(r2) // Right { _value: 'ZS' }

IO (函子)

  • _value是一个函数,这里是吧函数作为值来处理
  • 可以吧不纯的动作存储到_value中,延迟执行这个不纯的操作(惰性执行),包装当前的操作纯函数
  • 把不纯的操作交给调用者来处理
const fp = require('loadsh/fp')
class IO {
    static of (value){
        return new IO(()=>{
            return value
        })
    }
    constructor(fn){
        this._value=fn
    }
    map(fn){
        return new IO(fp.flowRight(fn,this._value))
    }
    value(){
        return this._value()
    }
}
// 当前在node环境中  调用
const r = IO.of(process).map(process=>process.execPath)
console.log(r) // IO { _value: [Function (anonymous)] }
console.log(r.value()) // /Users/zhaozhongyang/.nvm/versions/node/v13.13.0/bin/node

folktale

一个标准的函数式编程库

  • 与loadsh、ramda不同的是,他没有提供很多功能函数
  • 只提供了一些函数式处理的操作,例如 compose,curry等,一些函子 Task、Either、MayBe等

基本使用

const {
    compose,
    curry
} = require('folktale/core/lambda')

const _ = require("loadsh")

// curry 需要传递两个参数 第一个参数是传入函数的所需参数数量
let f = curry(2,(x,y)=>x+y)
console.log(f(1)(2)) // 3

// compose函数与 loadsh中的 flowRight 基本一致
let f2 = compose( _.toUpper,_.first)
console.log(f2(['a','b'])) // A

folktale——task(函子) 异步任务

const { task } =require('folktale/concurrency/task')
const fs = require('fs')
const fp = require('loadsh/fp')
// 文件处理使用task 函子
const readFile = (filename)=>task(resolver=>{
    fs.readFile(filename,'utf-8',(err,data)=>{
        if(err){
            resolver.reject(err)
        }
        resolver.resolve(data)
    })
})

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

folktale——Pointed(函子)

  • 实现了of静态方法的函子
  • of 是为了避免使用new来创建对象,更深层次的含义是of方法吧值放到了上下文Context(把值放到容器中,使用map方法来处理值)
class Container {
    constructor(value){
        this._value = value
    }
    map(fn){
        return new Container(fn(this._value))
    }
}

folktale——Monad(函子)

  • 实现了of静态方法的函子
  • of 是为了避免使用new来创建对象,更深层次的含义是of方法吧值放到了上下文Context(把值放到容器中,使用map方法来处理值)
  • 解决函子嵌套

IO函子的问题

函子嵌套

// 模拟 linux cat 命令
const readFile = (filename)=>new IO(()=>{
    return fs.readFileSync(filename,'utf-8')
})
const print = (x)=>new IO(()=>{
    console.log(x._value())
    return x
})


const cat = fp.flowRight(print,readFile)

const f = cat('package.json')._value()._value()
console.log(f)

实现方式 扁平化处理

const fs = require('fs')
const fp = require('loadsh/fp')
class IO {
    static of (value){
        return new IO(()=>{
            return value
        })
    }
    constructor(fn){
        this._value=fn
    }
    map(fn){
        return new IO(fp.flowRight(fn,this._value))
    }
    join(){
        return this._value()
    }
    flatMap(fn){
        return this.map(fn).join()
    }
}

// 模拟 linux cat 命令
const readFile = (filename)=>new IO(()=>{
    return fs.readFileSync(filename,'utf-8')
})
const print = (x)=>new IO(()=>{
    return x
})

const f = readFile('package.json').map(x=>fp.toUpper(x)).flatMap(print).join()
console.log(f)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章