Метод Compose в Repose библиотеки - PullRequest
0 голосов
/ 25 декабря 2018

Я смотрел на функцию compose в библиотеке перекомпоновки @acdlite для составления граничных условий для компонентов высшего порядка, и вот как выглядит функция компоновки

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

Однако я попробовал Эрик-Эллиот подход с одним вкладышем для составления, особенно из 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);

, и они оба, кажется, работают нормально.Я не могу понять, есть ли какая-либо разница в том, как работают вышеуказанные функции, и если да, то каковы они.

Ответы [ 3 ]

0 голосов
/ 26 декабря 2018

Если вас интересует какая структура на самом деле строит Reduce-Композиция, вы можете визуализировать ее следующим образом:

/* 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)) на Template-String, а arg => arg на вспомогательную функцию $_.Затем я поместил Template-String в скобки, чтобы результирующий String представлял IIFE .И последнее, но не менее важное: я передаю $_ вспомогательные функции с собственными именами compose.

$_ немного странно, но очень полезно визуализировать неприменимые / частично примененные функции.

Из вычислительной структуры видно, что Reduce-Composition строит вложенную структуру анонимных функций, а операции отдыха / распространения разбросаны по всему коду.

Визуализация и интерпретация частично примененных функций затруднительны.Мы можем упростить это, опустив внутреннюю анонимную функцию:

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

Я считаю, что визуализация сложных вычислений, подобных этой, является мощным методом правильного их понимания и лучшего понимания вложенных / рекурсивных вычислительных структур.

0 голосов
/ 26 декабря 2018

Реализация покажет и расскажет?Хорошо -

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'
0 голосов
/ 25 декабря 2018

Есть несколько отличий для очень нишевых сценариев, о которых полезно знать.

Первый предварительно создает функцию, что означает, что она вызывает 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 также размещено в стеке.

...