Текущий преобладающий подход предусматривает, что каждая функция с несколькими аргументами заключена в динамическую функцию карри. Хотя это помогает с проблемой № 1, остальные остаются нетронутыми. Вот альтернативный подход.
Составные функции
Составная функция каррируется только в своем последнем аргументе. Чтобы отличить их от обычных функций с несколькими аргументами, я называю их символом подчеркивания в конце (называть имена сложно).
const comp_ = (f, g) => x => f(g(x)); // composable function
const foldl_ = (f, acc) => xs => xs.reduce((acc, x, i) => f(acc, x, i), acc);
const curry = f => y => x => f(x, y); // fully curried function
const drop = (xs, n) => xs.slice(n); // normal, multi argument function
const add = (x, y) => x + y;
const sum = foldl_(add, 0);
const dropAndSum = comp_(sum, curry(drop) (1));
console.log(
dropAndSum([1,2,3,4]) // 9
);
За исключением drop
, dropAndSum
состоит исключительно из многоаргументных или составных функций, и все же мы достигли той же выразительности, что и с полностью каррированными функциями - по крайней мере, в этом примере.
Вы можете видеть, что каждая компонуемая функция принимает в качестве аргументов либо некаррированные, либо другие компонуемые функции. Это увеличит скорость, особенно для приложений с итеративными функциями. Однако это также является ограничительным, поскольку результат составной функции снова является функцией. Посмотрите пример countWhere
ниже для получения дополнительной информации.
Программное решение
Вместо того, чтобы определять составные функции вручную, мы можем легко реализовать программное решение:
// generic functions
const composable = f => (...args) => x => f(...args, x);
const foldr = (f, acc, xs) =>
xs.reduceRight((acc, x, i) => f(x, acc, i), acc);
const comp_ = (f, g) => x => f(g(x));
const I = x => x;
const inc = x => x + 1;
// derived functions
const foldr_ = composable(foldr);
const compn_ = foldr_(comp_, I);
const inc3 = compn_([inc, inc, inc]);
// and run...
console.log(
inc3(0) // 3
);
Операторные функции и функции высшего порядка
Возможно, вы заметили, что curry
(из первого примера) меняет местами аргументы, а composable
— нет. curry
предназначен для применения только к операторным функциям, таким как drop
или sub
, которые имеют разный порядок аргументов в форме с карри и без карри соответственно. Операторная функция — это любая функция, которая принимает только нефункциональные аргументы. В этом смысле...
const I = x => x;
const eq = (x, y) => x === y; // are operator functions
// whereas
const A = (f, x) => f(x);
const U = f => f(f); // are not operator but a higher order functions
Функции высшего порядка (HOF) не нуждаются в перестановке аргументов, но вы будете регулярно сталкиваться с ними с арностью выше двух, поэтому функция composbale
полезна.
HOF — один из самых замечательных инструментов в функциональном программировании. Они абстрагируются от применения функции. Именно по этой причине мы используем их все время.
Более серьезная задача
Можем решить и более сложные задачи:
// generic functions
const composable = f => (...args) => x => f(...args, x);
const filter = (f, xs) => xs.filter(f);
const comp2 = (f, g, x, y) => f(g(x, y));
const len = xs => xs.length;
const odd = x => x % 2 === 1;
// compositions
const countWhere_ = f => composable(comp2) (len, filter, f); // (A)
const countWhereOdd = countWhere_(odd);
// and run...
console.log(
countWhereOdd([1,2,3,4,5]) // 3
);
Обратите внимание, что в строке A
нам пришлось явно передать f
. Это один из недостатков компонуемых по сравнению с каррированными функциями: иногда нам нужно передавать данные явно. Однако, если вам не нравится безточечный стиль, это на самом деле преимущество.
Вывод
Создание компонуемых функций устраняет следующие проблемы:
- эстетические проблемы (менее частое использование рисунка карри
f(x) (y) (z)
- штрафы за производительность (гораздо меньше вызовов функций)
Однако пункт № 4 (читабельность) лишь немного улучшен (менее бесточечный стиль), а пункт № 3 (отладка) — совсем нет.
Хотя я убежден, что полностью каррированный подход лучше представленного здесь, я думаю, что стоит подумать о компонуемых функциях более высокого порядка. Просто используйте их до тех пор, пока вы или ваши коллеги не чувствуете себя комфортно с правильным каррированием.
person
Community
schedule
10.02.2017