【问题标题】:Compose method in recompose Libraryrecompose 库中的 Compose 方法
【发布时间】:2019-05-24 06:34:14
【问题描述】:

我正在查看 @acdlite 重构库中的 compose 函数,以组合高阶组件的边界条件,这就是 compose 函数的样子

const compose = (...funcs) => funcs.reduce((a, b) => (...args) => a(b(...args)), arg => arg);

但是,我尝试了Eric-Elliott 的单行方法来编写,从https://medium.com/javascript-scene/reduce-composing-software-fe22f0c39a1d,特别是这段代码。

const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);

我尝试在我的反应组件中使用这两种变体,像这样,

const ListWithConditionalRendering = compose(
  withLoadingIndicator,
  withDataNull,
  withListEmpty
)(Users);

而且它们似乎都可以正常工作。我无法理解上述功能的工作方式是否有任何不同,如果有,它们是什么。

【问题讨论】:

  • 一个使用Array的reduce函数,另一个使用reduceRight...
  • 第一个返回一个预先组合的函数,第二个返回一个作用域函数,当你调用它时应用组合。
  • @EmileBergeron :我的错,我可能不够清楚。是的,一个使用reduce,另一个使用reduceRight,但它们最后都给出了相同的结果。我理解使用reduceRight 的那个,但不能真正理解使用reduce 的那个,比如流程是如何工作的。你能解释一下吗?
  • @PatrickRoberts:有道理,谢谢。是否有任何负面情况或边缘情况,您可以想到其中一种会比另一种更好。另外,如果您可以详细说明将组合用作答案的预组合和作用域函数,我很乐意接受。

标签: javascript reactjs functional-programming reduce higher-order-functions


【解决方案1】:

对于非常小众的场景有一些差异,可能有助于了解这些差异。

第一个预先组合了一个函数,这意味着它在组合时调用reduce(),而不是在它被调用的时候。相反,第二种方法返回一个作用域函数,该函数在调用时调用reduceRight(),而不是在组合时调用。

第一种方法接受数组中最后一个函数的多个参数,而第二种方法只接受一个参数:

const compose1 = (...funcs) => funcs.reduce((a, b) => (...args) => a(b(...args)), arg => arg);
const compose2 = (...fns) => x => fns.reduceRight((v, f) => f(v), x);

const f = s => (...args) => (console.log('function', s, 'length', args.length), args);

compose1(f(1), f(2), f(3))(1, 2, 3);
compose2(f(4), f(5), f(6))(1, 2, 3);

如果函数数组非常大,第一种方法可能会导致堆栈溢出,因为它是预先组合的,而第二种方法(相对)†堆栈安全:

const compose1 = (...funcs) => funcs.reduce((a, b) => (...args) => a(b(...args)), arg => arg);
const compose2 = (...fns) => x => fns.reduceRight((v, f) => f(v), x);

const f = v => v;

try {
  compose1.apply(null, Array.from({ length: 1e5 }, () => f))();
  console.log('1 is safe');
} catch (e) {
  console.log('1 failed');
}

try {
  compose2.apply(null, Array.from({ length: 1e5 }, () => f))();
  console.log('2 is safe');
} catch (e) {
  console.log('2 failed');
}

† 如果...fns 太大,第二种方法仍然会导致堆栈溢出,因为arguments 也在堆栈上分配。

【讨论】:

  • 太棒了!接受。
【解决方案2】:

如果您对 reduce-composition 实际构建的结构感兴趣,可以将其可视化如下:

/* original:
const compose = (...funcs) =>
funcs.reduce((a, b) => (...args) => a(b(...args)), arg => arg);
*/

const compose = (...funcs) =>
  funcs.reduce((a, b) => `((...args) => ${a}(${b}(...args)))`, $_("id"));

const $_ = name =>
  `${name}`;

const id = x => x;
const inc = x => x + 1;
const sqr = x => x * x;
const neg = x => -x;

const computation = compose($_("inc"), $_("sqr"), $_("neg"));

console.log(computation);

/* yields:
((...args) => ((...args) => ((...args) =>
  id(inc(...args))) (sqr(...args))) (neg(...args)))
*/

console.log(eval(computation) (2)); // 5 (= id(inc(sqr(neg(2))))

那么这里发生了什么?我用模板字符串替换了内部函数(...args) => a(b(...args)),用$_ 辅助函数替换了arg => arg。然后我将 Template-String 包裹在括号中,这样生成的 String 代表一个 IIFE。最后但同样重要的是,我将具有正确名称的 $_ 辅助函数传递给 compose。

$_ 有点奇怪,但可视化未应用/部分应用的函数确实很有帮助。

从计算结构可以看出,reduce-composition 构建了匿名函数的嵌套结构,rest/spread 操作分散在整个代码中。

可视化和解释部分应用的函数很困难。我们可以通过省略内部匿名函数来简化它:

const compose = (...funcs) =>
  funcs.reduce($xy("reducer"), $_("id"));

const $_ = name =>
  `${name}`;

const $xy = name => (x, y) =>
  `${name}(${x}, ${y})`;

const id = x => x;
const inc = x => x + 1;
const sqr = x => x * x;
const neg = x => -x;

console.log(
  compose($_("inc"), $_("sqr"), $_("neg"))
  // reducer(reducer(reducer(id, inc), sqr), neg)
);

我们可以通过实际运行组合来进一步简化:

const compose = (...funcs) =>
  funcs.reduce((a, b) => (...args) => a(b(...args)), $x("id"));

const $x = name => x =>
  `${name}(${x})`;

console.log(
  compose($x("inc"), $x("sqr"), $x("neg")) (2) // id(inc(sqr(neg(2))))
);

我相信像这样的复杂计算的可视化是一种强大的技术,可以正确理解它们并更好地理解嵌套/递归计算结构。

【讨论】:

    【解决方案3】:

    实施展示和讲述?好的-

    const identity = x =>
      x
    
    const compose = (f = identity, ...fs) => x =>
      f === identity
        ? x
        : compose (...fs) (f (x))
        
    const add1 = x =>
      x + 1
      
    console .log
      ( compose () (0)                   // 0
      , compose (add1) (0)               // 1
      , compose (add1, add1) (0)         // 2
      , compose (add1, add1, add1) (0)   // 3
      )

    或者代替使用compose in-line ...

    const ListWithConditionalRendering = compose(
      withLoadingIndicator,
      withDataNull,
      withListEmpty
    )(Users);
    

    您可以在参数出现的情况下创建一种“前向组合”函数 -

    const $ = x => k =>
      $ (k (x))
      
    const add1 = x =>
      x + 1
      
    const double = x =>
      x * 2
    
    $ (0) (add1) (console.log)
    // 1
    
    $ (2) (double) (double) (double) (console.log)
    // 16
    
    $ (2) (double) (add1) (double) (console.log)
    // 10

    $ 在您可以保持以下模式时很有用 -

    $ (value) (pureFunc) (pureFunc) (pureFunc) (...) (effect)
    

    在上面,$ 将一个值放入某种“管道”中,但没有办法将值取出 。一个小的调整允许我们编写非常灵活的可变参数表达式。下面,我们使用$ 来分隔管道表达式的开始和结束。

    const $ = x => k =>
      k === $
        ? x
        : $ (k (x))
    
    const double = x =>
      x * 2
    
    const a =
      $ (2) (double) ($)
      
    const b =
      $ (3) (double) (double) (double) ($)
    
    console .log (a, b)
    // 4 24

    这个可变参数接口使您能够编写类似于其他更面向函数的语言中令人垂涎的|> 运算符的表达式 -

    value
      |> pureFunc
      |> pureFunc
      |> ...
      |> pureFunc
    
    5 |> add1
      |> double
      |> double
      // 24
    

    使用$,转换为-

    $ (value) (pureFunc) (pureFunc) (...) (pureFunc) ($)
    
    $ (5) (add1) (double) (double) ($) // 24
    

    该技术还可以很好地与柯里化函数混合 -

    const $ = x => k =>
      $ (k (x))
    
    const add = x => y =>
      x + y
      
    const mult = x => y =>
      x * y
      
    $ (1) (add (2)) (mult (3)) (console.log)
    // 9

    或者用一个更有趣的例子-

    const $ = x => k =>
      $ (k (x))
    
    const flatMap = f => xs =>
      xs .flatMap (f)
      
    const join = y => xs =>
      xs .join (y)
      
    const twice = x =>
      [ x, x ]
    
    $ ('mississippi')
      (([...chars]) => chars)
      (flatMap (twice))
      (join (''))
      (console.log)
      // 'mmiissssiissssiippppii'

    【讨论】:

      猜你喜欢
      • 2021-07-05
      • 2018-06-29
      • 2019-04-24
      • 2018-09-27
      • 2018-07-29
      • 2019-02-04
      • 2017-05-29
      • 1970-01-01
      • 2018-09-27
      相关资源
      最近更新 更多