перевод
Функциональное программирование — это стиль программирования, который пытается передать функцию как параметр (то есть как функцию обратного вызова) и вернуть функцию, но без побочных эффектов функции (побочные эффекты функции, которые изменяют состояние программы). Есть много языков, которые используют этот стиль программирования, включая некоторые очень популярные языки программирования, такие как JavaScript, Haskell, Clojure, Erlang и Scala. Функциональное программирование, с его возможностью передавать и возвращать функции, приносит много концепций:
- чистая функция
- карри
- Функции высшего порядка Одной из концепций, которую мы собираемся увидеть, является каррирование. В этой статье мы увидим, как работает каррирование и как оно влияет на нашу работу как разработчиков программного обеспечения.
Что такое карри
Каррирование — это процесс в функциональном программировании, который преобразует функцию, принимающую несколько аргументов, в очередь вложенных функций, а затем возвращает новую функцию со встроенными аргументами, ожидающими следующего. Он продолжает возвращать новую функцию (ожидая текущие параметры, как мы говорили ранее) до тех пор, пока все параметры не будут израсходованы. Эти параметры сохраняются «живыми» от уничтожения (используя свойства замыканий), и когда последняя функция в цепочке каррирования возвращается и выполняется, все параметры используются для выполнения.
Каррирование — это преобразование функции с несколькими арностями в функцию с меньшим количеством арностей. ——kbrainwaveПримечание. Термин арность: относится к количеству аргументов функции, например:
function fn(a, b) {
//...
}
function _fn(a, b, c) {
//...
}
Функция fn имеет два параметра (т.е. функция 2-арности), а _fn имеет три параметра (т.е. функция 3-арности). Таким образом, каррирование превращает функцию с несколькими аргументами в серию функций только с одним аргументом. Ниже мы рассмотрим простой пример:
function multiply(a, b, c) {
return a * b * c;
}
Эта функция принимает три числа, умножает их и возвращает результат вычисления.
multiply(1,2,3); // 6
Далее давайте посмотрим, как мы можем вызвать функцию умножения с полными аргументами. Давайте создадим каррированную версию функции и посмотрим, как вызвать одну и ту же функцию (и получить тот же результат) в серии вызовов.
function multiply(a) {
return (b) => {
return (c) => {
return a * b * c
}
}
}
log(multiply(1)(2)(3)) // 6
У нас есть (1,2,3) вызов функции в виде умножения на умножение (1) (2) в виде (3) множественных вызовов функций. Отдельная функция преобразована в набор функций. Для того, чтобы получить три цифровых результата умножения 1,2,3, эти числа следует передать после передачи, так как каждое число будет предварительно заполнено при следующем вызове функции. Мы можем выделить шаг вызова функции умножения (1) (2) (3), немного более понятный.
const mul1 = multiply(1);
const mul2 = mul1(2);
const result = mul2(3);
log(result); // 6
Давайте будем передавать параметры один за другим. Сначала передайте параметр 1 функции умножения:
let mul1 = multiply(1);
Выполнение приведенного выше кода вернет функцию:
return (b) => {
return (c) => {
return a * b * c
}
}
Теперь переменная mul1 будет содержать приведенное выше определение функции, которая принимает параметр b. Вызываем функцию mul1, передавая параметр 2:
let mul2 = mul1(2);
После выполнения функции mul1 она вернет третью функцию
return (c) => {
return a * b * c
}
Эта возвращенная функция теперь хранится в переменной mul2. По сути, переменную mul2 можно понимать так:
mul2 = (c) => {
return a * b * c
}
Когда функция mul2 вызывается с параметром 3,
const result = mul2(3);
Он будет рассчитан с использованием параметров, переданных ранее: a=1, b=2, и результатом будет 6.
log(result); // 6
Как вложенная функция, функция mul2 может обращаться к области видимости переменных внешней функции, а именно к функциям умножения и функциям mul1. Вот почему функция mul2 может использовать переменные, определенные в уже выполненной функции, для выполнения вычисления умножения. Хотя функция давно вернулась и в памяти производилась сборка мусора. Но его переменные каким-то образом остаются «живыми».
Примечания: Вышеупомянутые переменные сохраняют активность — это функция закрытия.Если вы не понимаете, вы можете проверить статьи, связанные с закрытием, для получения дополнительной информации. Вы можете видеть, что три числа применяются к функции только с одним параметром, передаваемым за раз, и каждый раз, когда возвращается новая функция, стоящая до тех пор, пока все параметры не будут израсходованы. Вот еще один пример:
function volume(l,w,h) {
return l * w * h;
}
const aCylinder = volume(100,20,90) // 180000
Выше приведена функция, которая вычисляет объем любой твердой формы. Эта каррированная версия будет принимать один аргумент и возвращать функцию, которая также принимает один аргумент и возвращает новую функцию. Затем цикл/продолжайте так, пока не будет достигнут последний параметр и не будет возвращена последняя функция. Затем выполните умножение предыдущего параметра на последний параметр.
function volume(l) {
return (w) => {
return (h) => {
return l * w * h
}
}
}
const aCylinder = volume(100)(20)(90) // 180000
Так же, как и предыдущая функция умножения, конечная функция принимает только один параметр h, но по-прежнему оперирует другими переменными в области видимости тех функций, которые уже вернулись из выполнения. Это возможно благодаря свойствам замыканий.
Примечание переводчика: приведенное выше очень многословно, и я чувствую, что другие примеры являются полностью повторяющимися объяснениями. Идея каррирования на самом деле состоит в том, чтобы взять функцию и получить функцию, которая возвращает специальную функцию.
Применение каррирования в математике
Мне нравятся математические инструкции 👉ВикипедияДалее демонстрируется концепция каррирования. Давайте подробнее рассмотрим каррирование на нашем примере. Предположим, что существует уравнение
f(x,y) = x^2 + y = z
Есть две переменные x и y. Если этим двум переменным присвоить x = 3 и y = 4 соответственно, можно получить значение z. Ниже подставим значения переменных y=4 и x=3 в функцию f(x,y):
f(x,y) = f(3,4) = x^2 + y = 3^2 + 4 = 13 = z
Результат получения z равен 13 Мы также можем каррировать f(x,y) и предоставлять эти переменные в серии функций.
h = x^2 + y = f(x,y)
hy(x) = x^2 + y = hx(y) = x^2 + y
[hx => w.r.t x] and [hy => w.r.t y]
Примечание: hx представляет идентификатор, индекс которого h равен x, и аналогично hy представляет идентификатор, индекс которого h равен y. w.r.t (по отношению к), математический символ, представляет около, часто используется для вывода или для удовлетворения определенных условий и т.п.
Мы делаем переменную x=3 для уравнения f(x,y)=x^2+y, и оно вернет новое уравнение с y в качестве переменной.
h3(y) = 3^2 + y = 9 + y
注:h3 表示h下标为3的标识符
Также эквивалентно:
h3(y) = h(3)(y) = f(3,y) = 3^2 + y = 9 + y
Результат функции по-прежнему не определен, но возвращает новое уравнение 9+y, которое ожидает другую переменную y. Далее мы передаем y=4
h3(4) = h(3)(4) = f(3,4) = 9 + 4 = 13
Переменная y является последней в цепочке переменных, затем выполняется сложение с предыдущей зарезервированной переменной x=3, значение окончательно разрешается, и результат равен 12. Таким образом, мы преобразуем это уравнение f(x,y)=3^2+y в серию уравнений, прежде чем получим окончательный результат.
3^2 + y -> 9 + y
f(3,y) = h3(y) = 3^2 + y = 9 + y
f(3,y) = 9 + y
f(3,4) = h3(4) = 9 + 4 = 13
Что ж, вот несколько применений каррирования в математике, если вы думаете, что это недостаточно ясно. допустимыйВикипедияПодробнее.
Каррирование и частично применяемые функции
Теперь некоторые могут начать думать, что количество вложенных функций каррированной функции зависит от аргументов, которые она принимает. Да, это карри. Я могу спроектировать объем каррированной функции следующим образом:
function volume(l) {
return (w, h) => {
return l * w * h
}
}
Итак, его можно назвать так:
const hCy = volume(70);
hCy(203,142);
hCy(220,122);
hCy(120,123);
или это:
volume(70)(90,30);
volume(70)(390,320);
volume(70)(940,340);
Мы только что определили специальные функции для вычисления объема цилиндра любой длины (l), 70. Он принимает 3 аргумента и имеет 2 уровня вложенных функций, в отличие от предыдущей версии, которая принимает 3 аргумента и имеет 3 уровня вложенных функций. Но эта версия не каррируется. Мы только что сделали частично примененную функцию громкости. Каррирование связано с частично применяемыми функциями, но это разные концепции. Частично прикладная функция — это преобразование функции в функцию с меньшим количеством элементов (то есть с большим количеством параметров).
function acidityRatio(x, y, z) {
return performOp(x,y,z)
}
|
V
function acidityRatio(x) {
return (y,z) => {
return performOp(x,y,z)
}
}
Примечание. Я намеренно не реализовал функцию PerformOp. Потому что здесь это не нужно. Все, что вам нужно знать, это концепции каррирования и частично примененных функций. Это частичное применение функции acidityRatio, не связанное с карри. Функцию acidityRatio следует использовать для приема меньшего количества аргументов, ожидая меньше аргументов, чем исходная функция. Каррирование можно сделать так:
function acidityRatio(x) {
return (y) = > {
return (z) = > {
return performOp(x,y,z)
}
}
}
Каррирование — это создание вложенных функций на основе количества аргументов функции, каждая из которых принимает один аргумент. Если нет параметров, то и каррирования нет. Может возникнуть ситуация, когда каррирование и частичное применение встречаются друг с другом. Предположим, у нас есть функция:
function div(x,y) {
return x/y;
}
Если выписать частично примененную форму, то получится результат:
function div(x) {
return (y) => {
return x/y;
}
}
Точно так же каррирование дает тот же результат:
function div(x) {
return (y) => {
return x/y;
}
}
Хотя каррирование и частичное применение функций дают один и тот же результат, это две разные сущности. Как мы уже говорили ранее, каррирование связано с некоторыми приложениями, но на самом деле его дизайн совершенно другой. То же самое в том, что они оба полагаются на замыкания.
Полезно ли каррирование функций?
Конечно работает, каррирование может сразу пригодиться, если вы хотите:
1. Пишите небольшие блоки кода, которые можно легко повторно использовать и настраивать, как мы это делаем с npm:
Например, предположим, что у вас есть продуктовый магазин, и вы хотите предоставить покупателям со скидкой 10% скидку (т. е. 10% скидку):
function discount(price, discount) {
return price * discount
}
Когда привилегированный клиент покупает товар стоимостью 500 долларов, вы даете ему скидку:
const price = discount(500,0.10); // $50
// $500 - $50 = $450
Вы можете предвидеть, что в долгосрочной перспективе мы будем рассчитывать скидку 10% каждый день:
const price = discount(1500,0.10); // $150
// $1,500 - $150 = $1,350
const price = discount(2000,0.10); // $200
// $2,000 - $200 = $1,800
const price = discount(50,0.10); // $5
// $50 - $5 = $45
const price = discount(5000,0.10); // $500
// $5,000 - $500 = $4,500
const price = discount(300,0.10); // $30
// $300 - $30 = $270
Мы можем изменить функцию скидки, чтобы нам не приходилось каждый раз увеличивать скидку на 0,01.
function discount(discount) {
return (price) => {
return price * discount;
}
}
const tenPercentDiscount = discount(0.1);
Теперь мы можем просто рассчитать стоимость товаров, купленных вашими клиентами:
tenPercentDiscount(500); // $50
// $500 - $50 = $450
Точно так же некоторые Привилегированные клиенты более важны, чем некоторые Привилегированные клиенты — назовем их Суперклиентами. И мы хотим дать этим суперклиентам скидку 20%. Мы можем использовать нашу функцию каррированной скидки:
const twentyPercentDiscount = discount(0.2);
Мы скорректировали скидку до 0,2 (20%) с помощью этой функции карри-скидки и настроили новую функцию для нашего суперклиента. Возвращаемая функция twoPercentDiscount будет использоваться для расчета скидки для нашего суперклиента:
twentyPercentDiscount(500); // 100
// $500 - $100 = $400
twentyPercentDiscount(5000); // 1000
// $5,000 - $1,000 = $4,000
twentyPercentDiscount(1000000); // 200000
// $1,000,000 - $200,000 = $600,000
2. Избегайте частого вызова функций с одними и теми же параметрами
Например, у нас есть функция, которая вычисляет объем цилиндра.
function volume(l, w, h) {
return l * w * h;
}
Бывает, что все цилиндры на складе имеют высоту 100 метров, вы увидите, что вы будете вызывать эту функцию несколько раз, где h равно 100 метрам.
volume(200,30,100) // 2003000l
volume(32,45,100); //144000l
volume(2322,232,100) // 53870400l
Чтобы решить вышеуказанные проблемы, вы можете использовать функцию громкости Collin (сделайте это, прежде чем мы сделаем):
function volume(h) {
return (w) => {
return (l) => {
return l * w * h
}
}
}
Мы можем определить функцию, которая конкретно определяет высоту цилиндра:
const hCylinderHeight = volume(100);
hCylinderHeight(200)(30); // 600,000l
hCylinderHeight(2322)(232); // 53,870,400l
Общая функция каррирования
Давайте разработаем функцию, которая принимает любую функцию и возвращает каррированную версию функции. Для этого у нас было бы так (хотя ваш метод может не совпадать с моим):
function curry(fn, ...args) {
return (..._arg) => {
return fn(...args, ..._arg);
}
}
Что делает приведенный выше код? Функция карри принимает функцию, которую мы хотим каррировать (fn), и некоторое переменное количество аргументов (…args). Остальные операции используются для сбора количества аргументов после fn в ... args. Затем верните функцию, которая аналогичным образом собирает оставшиеся аргументы, как...аргументы. Эта функция вызывает исходную функцию fn, используя оператор распространения в качестве аргументов, передавая ... args и ...args возвращает используемое значение. Теперь мы можем использовать функцию curry для создания определенных функций. Далее мы используем функцию карри для создания более конкретных функций для расчета медицинских осмотров (одна из которых — вычисление объема цилиндра высотой 100 метров).
function volume(l,h,w) {
return l * h * w
}
const hCy = curry(volume,100);
hCy(200,900); // 18000000l
hCy(70,60); // 420000l
Эпилог
Замыкания делают возможным каррирование JavaScript. Возможность сохранять состояние уже выполненных функций позволяет нам создавать фабричные функции — функции, которые могут добавлять определенные значения к своим аргументам. Каррирование, замыкания и функциональное программирование сложны. Но я могу гарантировать, потратьте время и потренируйтесь, и вы начнете осваивать это и увидите, насколько это ценно.
Ссылаться на
Карри — Википедия Частично примененные функции(над)
постскриптум
Вышеупомянутые переводы используются только для обучения и обмена, а уровень ограничен.Неизбежны ошибки, пожалуйста, поправьте меня.