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)