scriptQJ闯关之函数编程compose

227 阅读2分钟
原文链接: zhuanlan.zhihu.com

最近刚接触scriptQJ,点此通往scriptQJ,其中一些题目还是能够将一些编程的概念落实到实践中的。去使用一些上面的题目不是蛮多,希望能够有更丰富的题目,从中选择了一道函数编程的compose,其中题目如下:


在函数式编程当中有一个很重要的概念就是函数组合,实际上就是把处理数据的函数像管道一样连接起来,然后让数据穿过管道得到最终的结果。例如:

const add1 = (x) => x + 1
const mul3 = (x) => x * 3
const div2 = (x) => x / 2

div2(mul3(add1(add1(0)))) // => 3

而这样的写法可读性明显太差了。我们可以构建一个 compose 函数,它接受任意多个函数作为参数(这些函数都只接受一个参数),然后 compose 返回的也是一个函数,达到以下的效果:

const operate = compose(div2, mul3, add1, add1)
operate(0) // => 相当于 div2(mul3(add1(add1(0))))
operate(2) // => 相当于 div2(mul3(add1(add1(2))))

简而言之:compose 可以把类似于 f(g(h(x))) 这种写法简化成 compose(f, g, h)(x)。请你完成 compose 函数的编写。

额外挑战:你能通过 1~2 行代码实现 compose 吗。

const compose = () => {
  /* TODO */
}

好,基于题目的分析,是fn(...args)(x)类型的,因此很容易确定compose肯定是这样的形式:

const compose = (...args) => {
  // TODO
  return (x) => {
    // TODO
  }
}

下一步确定需要实现一个管道将函数串通起来,其中分析需求,是将传入的函数按从右到左执行,compose(fn1, fn2, fn3)(x),执行顺序即fn3() -> fn2() -> fn1(),并将上一个函数作为参数传入到下一个函数执行,基于此我们想到Array的reduceRight恰好可以作为这样的管道,接下来实现即:

const compose = (...args) => {
  let fns = args
  return val => {
    let firstFn = fns.pop()
    let result = fns.reduceRight((sum, currentFn) => {
      return currentFn(sum)
    }, firstFn(val))
    return result
  }
}

至此,实现结束。。。

然鹅,我们注意到了最后一句。。。。“额外挑战:你能通过 1~2 行代码实现 compose 吗。”

看大神@ackerman的实现:

const compose = (...fns) => {
  return val => fns.reduceRight((sum, currentFn) => currentFn(sum), val)
}

只能感叹在单个参数时的简洁之美,在支持多个参数的实现看到有目测是基于ackerman的实现的,这里贴出一个支持多参的版本:

const compose = (...fns) => {
  return (...args) => fns.reduceRight((args, fn) => fn(...[].concat(args)), [].concat(args))
}