乐趣区

关于javascript:JavaScript函数式编程之函子

函子(Functor)

函子是一个非凡的容器,通过一个一般对象来实现,该对象具备 map 办法,map办法能够运行一个函数对值进行解决(变形关系),容器 蕴含值和值变形关系(这个变形关系就是函数)。函数式编程中解决副作用的存在

  • 函数式编程的运算不间接操作值,,而是由函子实现
  • 函子就是一个实现了 map 契约的对象
  • 咱们能够把函子设想成一个盒子,盒子外面封装了一个值
  • 想要解决盒子中的值,咱们须要给盒子的 map 办法传递一个解决值的函数(纯函数),由这个函数来对值进行解决
  • 最终 map 办法返回一个蕴含新值所在的盒子(函子)

依据函子的定义咱们创立一个函子

// functor 函子
class Container {constructor (value) {
    // 函子外部保留这个值。下划线是不想内部拜访
    this._value = value
  }

  // map 办法接管一个解决值的函数
  map (fn) {return new Container(fn(this._value))
  }
}

此时就曾经创立了一个函子然而这是面向对象的形式来创立的,换成用函数式编程来写一个函子

class Container {constructor (value) {this._value = value}

  map (fn) {return Container.of(fn(this._value))
  }

  static of (value) {return new Container(value)
  }
}

let x = Container.of(5).map(x => x + 1).map(x => x - 1)

然而这个函子还是存在一些问题,比方空值的时候就会报错, 会让咱们的函子变的不纯,咱们须要去拦挡空值谬误,咱们创立一个办法去判断是否为空值,如果是管制咱们间接返回一个空值的函子,如果有值再去解决,这个时候就须要应用 MayBe 函子

let x = Container.of(null).map(x => x + 1).map(x => x - 1)

MayBe 函子

咱们在编程的过程中可能会遇到很多谬误,须要对这些谬误做相应的解决,MayBe函子的作用就是能够对外部的空值状况做解决(管制副作用在容许的范畴)

// MayBe 函子
class MayBe {constructor (value) {this._value = value}

  map (fn) {return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
  }

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

  static of (value) {return new MayBe(value)
  }
}

let x = MayBe.of(null)
  .map(x => x + 1)
  .map(x => x - 1)
console.log(x)

这个时候咱们曾经能失常执行了,然而当初呈现了空值的函子,然而咱们不晓得那个中央呈现了空值,所以咱们创立两个函子一个是失常的解决一个是呈现谬误状况解决,失常的就依照失常的形式创立,谬误的是是否咱们把 map 办法革新一下让她不再解决回调函数,间接返回一个空值的 MayBe 函子,这样就记录下了错误信息Eitcher 函子就是来解决这种状况的

Either 函子

Eitcher 相似于 if else 的解决,两者中的任何一个,异样会让函数变的不纯,Eitcher函子能够用来做异样解决

// 因为是二选一,所以定义两个类 Left 和 Right

// 记录错误信息的
class Left {constructor (value) {this._value = value}

  map (fn) {return this}

  static of (value) {return new Left(value)
  }
}

// 失常解决
class Rgiht {constructor (value) {this._value = value}

  map (fn) {return Rgiht.of(fn(this._value))
  }

  static of (value) {return new Rgiht(value)
  }
}

function parseJson (str) {
  try {return Rgiht.of(JSON.parse(str))
  } catch (err) {return Left.of({ message: err.message})
  }
}

// 成心传入谬误的数据
let r = parseJson('{ name:"2"}')
r.map(x => x.name.toUpperCase())
console.log(r)

IO 函子

IO 函子中的 _value 是一个函数,这里把函数作为值来解决,IO 函子能够吧不纯的动作贮存到 _value 中,提早这个不纯的操作(惰性执行),保障以后的操作是纯的,提早把不纯的操作到调用者来解决

const fp = require('lodash/fp')

// IO 函子
class IO {constructor (fn) {this._value = fn}
  static of (value) {return new IO(function () {return value})
  }
  map (fn) {
    // 把以后的 value 和传入的 fn 函数组合成一个新的函数
    return new IO(fp.flowRight(fn, this._value))
  }
}

let r = IO.of(process).map(x => x.execPath)

console.log(r)
console.log(r._value())

IO 函子外部帮咱们包装了一些函数,当咱们传递函数的时候有可能这个函数是一个不纯的操作,不论这个函数纯与不纯,IO 这个函子在执行的过程中它返回的这个后果始终是一个纯的操作,咱们调用 map 的时候始终返回的是一个函子,然而 IO 函子这个 _value 属性他外面要去合并很多函数,所以他外面可能是不纯的,把这些不纯的操作提早到了调用的时候,也就是咱们通过 IO 函子管制了副作用的在可控的范畴内产生

实现 liunx 下 cat 命令

const fp = require('lodash/fp')

// IO 函子
class IO {constructor (fn) {this._value = fn}
  static of (value) {return new IO(function () {return value})
  }
  map (fn) {
    // 把以后的 value 和传入的 fn 函数组合成一个新的函数
    return new IO(fp.flowRight(fn, this._value))
  }
}

let r = IO.of(process).map(x => x.execPath)

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

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

let cat = fp.flowRight(print, readFile)

console.log(cat('package.json')._value()._value())

此时 IO 函子呈现了嵌套的问题,导致调用嵌套函子中的办法就必须要要._value()._value() 这样来执了,嵌套了几层就须要几层调用

Folktale

Folktale 是一个规范的函数式编程库,和 lodash 不同的是,他没有提供很多性能函数,只提供了一些函数式解决的操作,例如:compose、curry等,一些函子 Task、Either、MayBe等,

Folktale 中的 currycompose 的简略应用

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

// 与 lodash 区别,第一个参数指明前面参数的个数
let f = curry(2, (n1, n2) => n1 + n2)

console.log(f(1, 2))

// compose 就是函数组合 lodash 中的函数组合是 flowRight
let f2 = compose(toUpper, first)

console.log(f2(['one', 'two']))

Folktale 中的 task 函子

函子能够解决异步工作,在异步工作中会通往天堂之门的回调,而应用task 函子能够防止回调的嵌套,具体请看官网文档

// Task 异步工作
const {task} = require('folktale/concurrency/task')
const {split, find} = require('lodash/fp')
const fs = require('fs')

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)
    }
  })

Pointed 函子

Pointed 函子 是实现了 of 静态方法,of 办法是为了防止应用 new 来创建对象,更深层次含意是 of 办法把值放到上下文Context(把值放到容器中,应用map 来解决值)

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

Monad 函子

解决函子嵌套的问题,Monad 函子是能够变扁的 Pointed 函子 IO(IO),一个函子如果具备 joinof两个办法并遵循一些定律就是一个Monad

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

  join () {return this._value()
  }

  // 同时调用 join 和 map
  flatMap (fn) {return this.map(fn).join()}
}

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

function print (x) {return new IO(() => {return x})
}

let r = readFile('package.json').flatMap(print).join()

console.log(r)

当咱们想要去调用一个办法,这个办法返回一值的时候咱们去调用 map 办法,当咱们想要去调用一个办法,这个办法返回一个函子的时候咱们去调用 flatMap 办法

原文地址:https://kspf.xyz/archives/17
更多内容微信公众号搜寻 充饥的泡饭
小程序搜一搜 开水泡饭的博客

退出移动版