重组库中的撰写方法

时间:2018-12-25 18:03:44

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

我一直在使用@acdlite查看重组库中的compose函数,以为高阶组件合成边界条件,这就是看起来像compose函数的样子

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

但是,我尝试了Eric-Elliotthttps://medium.com/javascript-scene/reduce-composing-software-fe22f0c39a1d的一种线性方法,具体来说就是这段代码。

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

我尝试在我的react组件中同时使用这两种变体,

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

,它们似乎都可以正常工作。我无法理解上述功能的工作方式是否存在差异,如果有的话,它们有什么区别。

3 个答案:

答案 0 :(得分:3)

对于非常特殊的场景,有一些差异可能会有所帮助。

第一个函数是由函数组成的,这意味着它在构成时而不是在被调用时调用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也分配在堆栈上。

答案 1 :(得分:3)

如果您对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))))
);

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

答案 2 :(得分:1)

执行情况显示并告诉?好吧-

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 ...

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'