讨论compose/链式/管道问题
讨论compose/链式/管道问题

讨论compose/链式/管道问题

这三者其实都是一个东西,把函数组合起来,而函数可以组合起来是因为他们接受的和返回的是同一种类型。

像ml,compose可以写成 f o g,也可以写成g |=> f

解决什么问题:

  • 按一定顺序执行一组方法,对数据进行加工,像管道一样

实现一个简单为一个数指定一定顺序的操作:(ml递归compose版本)

const compose = (f, g) => (x) => f(g(x))
const reduceRecursion = (arr, f, acc) => {
  if (arr.length === 0) return acc
  if (typeof acc === 'undefined') acc = arr.shift()
  const current = arr.shift()
  return reduceRecursion(arr, f, f(acc, current)) // 每次current都会先是g,所以先接受参数,然后返回函数,这个函数又会成为下一个函数的f,下一个current又会先生成匿名函数先得到参数运行结果再传入f中
}
reduceRecursion([x => x + 1, x => x * 2], compose)(1) // 3 所以这里的顺序就是得到*2的结果再加1
// 那么如果想按顺序希望先执行+1
const composeOpposed = (f, g) => (x) => g(f(x))
reduceRecursion([x => x + 1, x => x * 2], composeOpposed)(1) // 4

考虑这个函数想加点副作用,比如说想扩充一个打印功能

reduceRecursion ([f => x => f(x+1), f => x => f(x*2)], compose)(console.log)(1) //  4

这里的情况就会发生改变,*2会先拿到console.log,然后返回匿名函数,+1会拿到*2的匿名函数,得到包含*2的匿名函数,然后1被传进+1的匿名函数里面。首先得到+1,+1后的结果会作为参数传入*2的匿名函数

在看一个typescript版本,接受sideEffect后返回函数,并应用sideEffect,此时被传递的参数变为带有sideEffect的函数,而不是最初的数字,相比之前拿到值,直接流入前一个函数,此时流入旧的函数不再是值,而是一个函数,得到的是旧函数包装函数,得到外界传值

type SideEffectFunType<T,R> = (f: (x: R) => T) => (x: R) => T
type ComposeFunType<T> = (x: T) => T

const compose: <T>(f: ComposeFunType<T>, g: ComposeFunType<T>) => ComposeFunType<T> = (f, g) => (x) => f(g(x))

const reduceRecursion: <T, R>(
  arr: (SideEffectFunType<T, R>)[], 
  f: (acc: SideEffectFunType<T, R>, current: SideEffectFunType<T, R>) =>  SideEffectFunType<T, R>, 
  acc?: SideEffectFunType<T, R>
  ) => SideEffectFunType<T, R> = (arr, f, acc) => {
  if(arr.length === 0) throw new Error("no value")
  if(arr.length === 1) return arr[0]
  const formatAcc = typeof acc === 'undefined'? arr.shift() : acc
  const current= arr.shift()
  return reduceRecursion(arr, f, f(formatAcc!, current!))  //每次current都会先是g,所以先接受参数,然后返回函数,这个函数又会成为下一个函数的f,下一个current又会先生成匿名函数先得到参数运行结果再传入f中
}

reduceRecursion<void, number> ([f => x => f(x+1), f => x => f(x*2)], compose)(console.log)(1)

再看看不是递归的实现,最初始那个函数会拿到外层的sideEffect,然后被新函数调用,新函数会是那个包装函数得到外界传值

function replace(arr, sideEffect) {
  arr.forEach(el => {
     sideEffect = el(sideEffect)
  })
  return sideEffect
}
replace([f => x => f(x+1), f => x => f(x*2)], console.log)(1)  // 3 这种不用compose直接每次替换成一个新的sideEffect,先+1拿到副作用,自己又被下一个函数当做副作用,最后那个函数会最先拿到参数
replace([x =>x+1, x => x*2], 1) 

迭代是先执行,前者会被后者当做参数调用

递归是因为compose是先g(x)再f(g(x)),所以是先执行后者,后者被当做参数调用

递归如果用的是composeOppsed那么顺序就跟迭代一样

递归版本要实现能最最后一个捕获错误就要用composeOppsed

reduceRecursion([fn => x => fn(x + 1), fn => x => { throw new Error('error') }, fn => x => {
  try {
    return fn(x)
  } catch (error) {
    console.log(error)
  }
}], composeOpposed)(console.log)(1)

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注