函数式编程(二)

157 阅读6分钟

函数的组合需要满足结合律

任意两个函数组合起来且跟原先的执行顺序一样的情况下。结果都一样。

const optiSplicing = (...agrs) => val => agrs.reverse().reduce((acc, fn) => fn(acc), val); 
const reverse = arr => arr.reverse();
const first = arr => arr[0];
const Font = arr => arr.toUpperCase();
// const newFn = optiSplicing(optiSplicing(Font, first), reverse);
const newFn = optiSplicing(Font, optiSplicing(first, reverse));
console.log(newFn(['wqw', 'asa']), 8888)
console.log(Font(first(reverse(['wqw', 'asa']))), 999)

函数组合——调试

const _ = require('lodash');
// const log = v => {
//   console.log(v);
//   return v;
// }
const trace = _.curry((tag, v) => {
  console.log(tag, v);
  return v;
})
// _.split(str, sep);
const split = _.curry((sep, str) => _.split(str, sep) );
// _.toLower()
// join
const join = _.curry((sep, str) => _.join(str, sep) );
const map = _.curry((fn, arr) => _.map(arr, fn))
const f = _.flowRight(join('-'), trace('map  之后'), map(_.toLower), trace('split 之后'), split(' '));

console.log(f("NEVER SAY DIE"))

总结:

我们需要将所有处理数组的函数进行柯里化。也就是我们传入一个或者小于形参都会返回一个新的函数。这里我传入的参数就是我们需要根据环境特别处理的参数。 然后我们需要将众多柯里化的函数需要进行执行。我们需要用到函数的组合。同时进行调试,和调试函数。

lodash/fp模块函数优先, 数据滞后. 被柯里化的函数,传入一个参数,会返回一个新的函数等待剩下的参数。

const _ = require('lodash');
const fp = require('lodash/fp');

const trace = _.curry((tag, v) => {
  console.log(tag, v);
  return v;
})
const f = fp.flowRight(fp.join('-'), trace('map  之后'), fp.map(_.toLower), trace('split 之后'), fp.split(' '));
console.log(f("NEVER SAY DIE"))

Point Free

Point Free : 我们可以把数据处理的过程定义与数据无关的合成运算。不需要用到代表数据的那个参数, 只要把简单的运算和在一起,在使用这种模式之前。我们需要定义一些辅助的基本运算函数。

  1. 不需要指明处理的数据。
  2. 只需要合成运算的过程。
  3. 需要定义一些辅助的基本运算函数。
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(' '))

Functor (函子)

什是函子

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


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

//   map (fn) {
//     return new Container(fn(this._value))
//   }
// }

// const r = new Container(5)
//   .map(x => x + 1)
//   .map(c => c * c)

class Container {
  // 静态方法。可以使用Container获取到。
  static of (value) {
    // 静态方法传入一个值。 返回一个new一个对象。
    return new Container(value)
  }
  // new的对象会传入这里
  constructor (value) {
    // 将this._value的值改变。
    this._value = value;
  }
  // map 是new Container 对象的方法。
  map (fn) {
    // 当调用map 方法的时候。 会有一个回调函数。这个回调函数是用来改变this._value的值的。
    // const val = fn(this._value);
    return Container.of(fn(this._value));
  }
}
// Container 外部我们可以通过Container.of()获取静态方法
// 这个静态方法是返回一个返回的是一个新对象。
// 会将Container.of(5)的参数赋值给this._value.
// 当我们调用Container的map对象的时候会用回调函数处理this._value的值。

const r = Container.of(5)
  .map(x => x + 1)
  .map(c => c * c)
console.log(r, 8)

总结:

函子一个函数对象一直维护这一个属性值。外界不会读取它,但是它抛出一个map的方法。可以改变它。

MayBe 函子

当Container.of传入 null 或者 undefined 其实传入是没有问题。也正常赋值给this._value 当我们使用回调的时候对this._value操作的时候可能就会报错。这与纯函数的理念不符。所以 我们需要控制这些, 所以我们需要做一些措施避免做这些事情发生。 为了解决调用Functor函子传入null和undefined;返回会报错。产生副作用。为了解决副作用。会写一个判断函数。

class Maybe {
  static of (value) {
    return new Maybe(value)
  }
  constructor (value) {
    this._value = value
  }
  map (fn) {
    // 判断传入点的值是null undefined 则重置this._value的值为null 不调用回调函数。
    // 这样则符合纯函数的理念。输入输出对应的。
    return this.isNothing() ? Maybe.of(null) : Maybe.of(fn(this._value))
  }
  isNothing () {
    return this._value === null || this._value === undefined
  }
}
const r = Maybe.of('undefined')
.map(x => x.toUpperCase())
.map(x => null)
.map(x => split(' ')
)
console.log(r);
// 存在的问题是链式调用。不知道那一块出现了问题。

Either 函子

为了解决 :存在的问题是链式调用。不知道那一块出现了问题。 简单来说将异常catch掉。


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 this.isNothing() ? Right.of(null) : Right.of(fn(this._value))
  }
  isNothing () {
    return this._value === null || this._value === undefined;
  }
}

// const r1 = Right.of(12).map(x => x + 2);
// const r2 = Left.of(12).map(x => x + 2);
// console.log(r1, r2, 888)
function parseJSON (str) {
  try {
    return Right.of(JSON.parse(str))
  }
  catch (e) {
    return Left.of({error: e.message})
  }
}
const r = parseJSON('{"name": "zs"}').map(x => x.name.toUpperCase());
console.log(r)

IO 函子

IO函子中的_value是一个函数, 这里是把函数作为值来处理。 IO函子可以把不纯的动作储存_value中, 延迟执行这个不纯的操作(惰性执行), 包装当前的纯操作。把不纯的留给调用这处理。

const fp = require('lodash/fp') class IO {

static of (x) { return new IO(function () { return x }) } 
constructor (fn) {

this._value = fn 
} 
map (fn) {

// 把当前的 value 和 传入的 fn 组合成一个新的函数

return new IO(fp.flowRight(fn, this._value)) }

}

// 调用 let io = IO.of(process).map(p => p.execPath) console.log(io._value())

处理异步任务

const {} = require()

pointed 函子

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

Monad(单子)

const fs = require('fs') const fp = require('lodash/fp')

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

// IO(IO(x)) 
let cat = fp.flowRight(print, readFile) 
// 调用 
let r = cat('package.json')._value()._value() 
console.log(r)

Monad 函子是可以变扁的 Pointed 函子,IO(IO(x)) 一个函子如果具有 join 和 of 两个方法并遵守一些定律就是一个 Monad

const fp = require('lodash/fp') // IO Monad 
class IO {

static of (x) { 
  return new IO(function () { return x }) 
  } 
  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() 
  }
}

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

JavaScript 异步编程

分析同步执行:

  1. 先预解析代码。先将所有的函数声明、变量声明。放在window对象下面注册登记一下(bar foo)。
  2. 开始执行代码。
console.log('global begin') // 率先执行 (1)
function bar () { // 路过这里已经声明过了。不管他。
  console.log('bar task') // 输出(3)
}
function foo () { // 同理
  console.log('foo task') // 输出(2)
  bar() // 这个也认识。
}
foo() // 老大这是谁呀。这人我认识。熟人好说,先执行了吧。
console.log('global end') // 输出(4)

同步好理解,安执行顺序压入栈中执行完毕后释放。

异步模式

console.log('global begin')
setTimeout(function timer1 () {
console.log('timer1 invoke')
}, 1800)
setTimeout(function timer2 () {
console.log('timer2 invoke')
setTimeout(function inner () {
console.log('inner invoke')
}, 1000)
}, 1000)
console.log('global end')
  1. 还是先预解析。没有声明
  2. 开始执行。输出global begin
  3. 碰到定时器浏览器自动开启倒计时线程。
  4. 同上
  5. 输出 'global end'
  6. 执行栈空挡期 Event loop 会循环问浏览器这个线程执行完毕。如果有执行完了 的来我这排队。我会一个一个送到执行栈中执行。直到所有执行完毕。

回调函数

回调可以理解为我们将某个函数当做参数, 传入某个函数里面执行。当这个函数执行的时候。就可以把当前作用域的数据传出来做一些事件。

由调用者定义,交给执行者的函数。就叫做回掉函数。