композиция функций с оператором rest, редуктором и картографом

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

const reducer = (acc, val) => acc.concat([val]);
const reduceWith = (reducer, seed, iterable) => {
  let accumulation = seed;

  for (const value of iterable) {
    accumulation = reducer(accumulation, value);
  }

  return accumulation;
}
const map =
  fn =>
    reducer =>
      (acc, val) => reducer(acc, fn(val));
const sumOf = (acc, val) => acc + val;
const power =
  (base, exponent) => Math.pow(base, exponent);
const squares = map(x => power(x, 2));
const one2ten = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
res1 = reduceWith(squares(sumOf), 0, one2ten);
const divtwo = map(x => x / 2);

Теперь я хочу определить оператор композиции

const more = (f, g) => (...args) => f(g(...args));

и я вижу, что он работает в следующих случаях

res2 = reduceWith(more(squares,divtwo)(sumOf), 0, one2ten);
res3 = reduceWith(more(divtwo,squares)(sumOf), 0, one2ten);

которые эквивалентны

res2 = reduceWith(squares(divtwo(sumOf)), 0, one2ten);
res3 = reduceWith(divtwo(squares(sumOf)), 0, one2ten);

Весь скрипт онлайн.

Я не понимаю, почему я не могу объединить последнюю функцию (sumOf) с оператором композиции (more). В идеале я хотел бы написать

res2 = reduceWith(more(squares,divtwo,sumOf), 0, one2ten);
res3 = reduceWith(more(divtwo,squares,sumOf), 0, one2ten);

но это не работает.

Изменить

Понятно, что моя первоначальная попытка была ошибочной, но даже если я определяю композицию как

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

Я до сих пор не могу заменить compose(divtwo,squares)(sumOf) на compose(divtwo,squares,sumOf)


person Community    schedule 17.05.2017    source источник
comment
... является не оператор!   -  person Felix Kling    schedule 17.05.2017
comment
@FelixKling Я пытаюсь написать что-то, что может сделать трансформацию (a,b,c,d, etc...) => a(b(c(d(etc...))))   -  person    schedule 17.05.2017
comment
Я понимаю проблему, которую вы пытаетесь решить. ... еще не оператор ;)   -  person Felix Kling    schedule 17.05.2017
comment
Преобразователи труднее понять, чем вы думаете. Вот упрощенный map преобразователь: map = f => g => x => y => g(x) (f(y)). Когда вы применяете map к композиции функций comp = f => g => x => f(g(x)), кажется, что comp может составлять более двух функций, потому что x — это просто еще одна функция. comp вместе с составными преобразователями создают стек преобразователей, который затем оценивается сверху вниз (и, следовательно, композиция выполняется слева направо). Эта способность comp называется абстракцией над арностью и представляет собой продвинутый материал функционального программирования.   -  person    schedule 17.05.2017
comment
Э-э, ваша функция more имеет только два параметра функции f и g, которые будут вложены друг в друга, как вы ожидаете, что она волшебным образом будет работать с большим количеством аргументов?   -  person Bergi    schedule 17.05.2017
comment
@Bergi, теперь понятно, но мой вопрос в том, почему с const compose = (...fns) => x => fns.reduceRight((v, fn) => fn(v), x); я не могу написать compose(divtwo,squares,sumOf) вместо compose(divtwo,squares)(sumOf)   -  person    schedule 17.05.2017
comment
@user1892538 user1892538 Проблема в том, что compose всегда нужно возвращать функцию — например, (...args) => … из вашего more. Ваш reduce этого не делает.   -  person Bergi    schedule 17.05.2017
comment
@Bergi взгляните на мой отредактированный ответ, пожалуйста: я думаю, что это помогает, вы согласны с тем, что мое решение действительно?   -  person    schedule 17.05.2017


Ответы (3)


Наконец-то я нашел способ реализовать композицию, которая работает нормально.

const more = (f, ...g) => {
  if (g.length === 0) return f;
  if (g.length === 1) return f(g[0]);
  return f(more(...g));
}

Лучшее решение

Вот еще одно решение с редуктором и без рекурсии

const compose = (...fns) => (...x) => fns.reduceRight((v, fn) => fn(v), ...x);
const more = (...args) => compose(...args)();

использование:

res2 = reduceWith(more(squares,divtwo,sumOf), 0, one2ten);
res3 = reduceWith(more(divtwo,squares,sumOf), 0, one2ten);

полный скрипт онлайн

person Community    schedule 17.05.2017
comment
reduceRight принимает только два аргумента. Здесь нет смысла использовать синтаксис rest/spread. Просто сделай x => fns.reduceRight((v, f) = f(v), x) - person Bergi; 18.05.2017
comment
@Bergi, но когда я пробую онлайн, кажется, что все по-другому, и в конце я получаю, что средство уменьшения ошибок не является функцией. Я сейчас на мобильном телефоне, поэтому могу ошибаться, но я открыл онлайн-ссылку, и мне следовало изменить ее, как вы мне сказали... кроме того, раньше, когда я публиковал ответ, я пробовал много разных возможностей, и это было только один успешный... В любом случае спасибо, перепроверю - person ; 18.05.2017
comment
Упс, кажется, я неправильно написал функцию стрелки в своем комментарии (забыл >), но моя точка зрения остается неизменной. - person Bergi; 18.05.2017
comment
@Bergi Берги Я заметил стрелку, но дело не в этом. Вот ссылка: я сейчас на ноутбуке, и я подтверждаю, что вижу, что средство уменьшения ошибок не является функцией (изображение консоли, но у вас есть текст в ссылке в начале этого комментария) - person ; 18.05.2017
comment
Конечно, вы по-прежнему вызываете more, который не работает, вместо функции compose с reduceRight. Вот фиксированный код: es6console.com/j2tl7eu5 - person Bergi; 18.05.2017
comment
@Bergi Я вижу [385,null,null] по вашей ссылке, а когда я звоню своей (последнее слово онлайн в ответе выше), я получаю [385,192.5,96.25] - person ; 18.05.2017
comment
Когда я пытаюсь запустить тот, который вы связали, я получаю Uncaught ReferenceError: res1 is not defined - переменные результата не имеют объявления и кода строгого режима. - person Bergi; 18.05.2017
comment
@Bergi Я копирую EcmaScript6 с помощью Ctrl+A (выбрать все) и Ctrl+C (копировать), а затем вставляю его (Ctrl+V) в консоль ниже... Я не знаю, я запутался - person ; 18.05.2017
comment
Нужно иметь возможность нажать кнопку [Выполнить] в левом верхнем углу :-) Но в любом случае ваша функция map уже выполняет композицию. Вы должны вызвать reduceWith(squares(divtwo(sumOf)), 0, one2ten); как здесь, compose ничего делать не нужно. Что вы могли бы сделать, так это что-то вроде reduceWith(map(compose(x => x/2, x=>x**2))(sumOf), 0, one2ten), где вы компонуете функции в новую функцию, которая затем используется в файле map. - person Bergi; 18.05.2017
comment
Итак, нажмите [Выполнить] по этой ссылке: мне кажется, все в порядке - person ; 18.05.2017
comment
Да, но это потому, что more вызывает compose действительно ужасным образом (и это требует отдыха/спреда, чтобы заставить redureRight работать без начального аккумулятора - ура). Вам лучше сделать что-то вроде это - person Bergi; 18.05.2017
comment
@Bergi, хорошо, почему бы тебе не опубликовать это как ответ? :-) - person ; 18.05.2017

Ваш more работает только с двумя функциями. И проблема в том, что здесь more(squares,divtwo)(sumOf) вы выполняете функцию, а здесь more(squares,divtwo, sumOf) вы возвращаете функцию, которая ожидает другого вызова (например, const f = more(squares,divtwo, sumOf); f(args)).

Чтобы иметь переменное количество составных функций, вы можете определить другой more для состава функций. Обычный способ составления любого количества функций - это compose или pipe функций (разница в порядке аргументов: pipe берет функции слева направо в порядке выполнения, compose - наоборот).

Обычный способ определения pipe или compose:

const pipe = (...fns) => x => fns.reduce((v, fn) => fn(v), x);

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

Вы можете изменить x на (...args), чтобы оно соответствовало вашему определению more.

Теперь вы можете выполнять любое количество функций одну за другой:

const pipe = (...fns) => x => fns.reduce((v, fn) => fn(v), x);

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

const inc = x => x + 1;
const triple = x => x * 3;
const log = x => { console.log(x); return x; } // log x, then return x for further processing

// left to right application
const pipe_ex = pipe(inc, log, triple, log)(10);

// right to left application
const compose_ex = compose(log, inc, log, triple)(10);

person Egor Stambakio    schedule 17.05.2017
comment
const more = (f, g) => (...args) => f(g(...args)); объединяет 2 функции и применяет комбинации к массиву args в качестве аргумента. здесь more(squares,divtwo,sumOf) вы пытаетесь определить третью функцию, которая нигде не используется в вашем more определении. - person Egor Stambakio; 17.05.2017
comment
Проблема здесь в том, что more(squares,divtwo)(sumOf) вы выполняете функцию, а здесь more(squares,divtwo, sumOf) вы возвращаете функцию, которая ожидает другого вызова (например, const f = more(squares,divtwo, sumOf); .... f(args)). И compose отлично работает, если вы добавите больше функций, но также выполните его с аргументами: compose(squares,divtwo,squares,divtwo)(sumOf) : jsfiddle.net/wostex/ ww7rdgsr . Чтобы вызвать more с одной парой скобок, вы должны определить его как const more = (f, g, ...args) => f(g(...args));, но это не имеет смысла. - person Egor Stambakio; 17.05.2017
comment
pipe принимает функции слева направо в порядке выполнения, compose — наоборот Я думаю, что это соглашение очень раздражает. Либо композицию/трубу справа налево, либо наоборот. Но не оба. Вы знаете, что преобразователи создают стек преобразования, который работает слева направо, потому что x в f(g(x)) — это другая функция? Так что на самом деле ваши преобразователи будут оцениваться справа налево в вашем pipe. - person ; 17.05.2017

Я до сих пор не могу заменить compose(divtwo,squares)(sumOf) на compose(divtwo,squares,sumOf)

Да, они не равнозначны. И вообще не стоит пытаться! Обратите внимание, что divtwo и squares являются преобразователями, а sumOf – редукторами. Они имеют разные типы. Не создавайте функцию more, которая их смешивает.

Если вы настаиваете на использовании динамического количества преобразователей, поместите их в массив:

[divtwo, squares].reduceRight((t, r) => t(r), sumOf)
person Bergi    schedule 17.05.2017
comment
Что ж, я принимаю ваш ответ с теоретической точки зрения, но вы уклоняетесь от технического вопроса :-) Я хотел смешать эти разные типы только ради понимания возможностей языка... Кстати, большое спасибо за вашу помощь. - person ; 18.05.2017
comment
Как мы видели в обсуждении ниже, есть много способов заставить это работать — один из них будет [divtwo, squares, sum].reduceRight((t, r) => t(r) /* no start value */) — который может быть абстрагирован во вспомогательные функции на разных границах, возможно, даже с использованием синтаксиса rest/spread. - person Bergi; 18.05.2017
comment
Ну и просто чтобы была рабочая ссылка - это компоновать консоль ES6 для произвольного количества функций (преобразователи ), из кода, предложенного в исходной статье: т. е. const compositionOf = (acc, val) => (...args) => val(acc(...args));, а затем const compose = (...fns) => reduceWith(compositionOf, x => x, fns); с использованием/примером compose(divtwo,squares)(sumOf), поэтому редуктор sumOf остается разделенным. Если вы хотите поделиться своей любимой версией скрипта, добро пожаловать - person ; 18.05.2017