8

Что такое 'Кюринг'?

16

Я встречал упоминания о каррированных функциях в нескольких статьях и блогах, но не могу найти хорошее объяснение (или хотя бы такое, которое действительно имеет смысл!).

5 ответ(ов)

11

Куррирование — это когда функция, принимающая несколько аргументов, разбивается на ряд функций, каждая из которых принимает только один аргумент. Вот пример на JavaScript:

function add(a, b) {
    return a + b;
}

add(3, 4); // возвращает 7

Это функция, которая принимает два аргумента, a и b, и возвращает их сумму. Теперь мы применим куррирование к этой функции:

function add(a) {
    return function(b) {
        return a + b;
    }
}

Теперь это функция, которая принимает один аргумент a и возвращает функцию, принимающую другой аргумент b. Эта внутренняя функция возвращает сумму a и b.

add(3)(4); // возвращает 7

var add3 = add(3); // возвращает функцию

add3(4); // возвращает 7
  • В первом случае возвращается 7, как и в случае вызова add(3, 4).
  • Во втором случае мы определяем новую функцию add3, которая будет добавлять 3 к своему аргументу. Это то, что некоторые могут назвать замыканием.
  • В третьем случае мы используем операцию add3, чтобы добавить 3 к 4, снова получая в результате 7.
1

Это может быть способ использовать функции для создания других функций.

В JavaScript:

let add = function(x) {
  return function(y) { 
    return x + y;
  };
};

Это позволяет нам вызывать её так:

let addTen = add(10);

Когда это выполняется, 10 передаётся как x:

let add = function(10) {
  return function(y) {
    return 10 + y;
  };
};

Что означает, что мы получаем эту функцию:

function(y) { return 10 + y; };

Таким образом, когда вы вызываете

addTen();

на самом деле вы вызываете:

function(y) { return 10 + y; };

Так что если вы сделаете это:

addTen(4);

это будет то же самое, что:

function(4) { return 10 + 4; } // 14

Таким образом, наша addTen() всегда добавляет десять ко всему, что мы передаем. Мы можем создать подобные функции таким же образом:

let addTwo = add(2);       // addTwo(); добавит два ко всему, что вы передадите
let addSeventy = add(70);  // ... и так далее...

Теперь очевидный вопрос: зачем вообще это делать? Это превращает ту, что была жадной операцией x + y, в ту, которую можно выполнять «лениво», что позволяет нам делать как минимум две вещи:

  1. кэшировать дорогие операции
  2. достигать абстракций в функциональной парадигме.

Представьте, что наша каррифицированная функция выглядит так:

let doTheHardStuff = function(x) {
  let z = doSomethingComputationallyExpensive(x);
  return function (y) {
    return z + y;
  }
}

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

let finishTheJob = doTheHardStuff(10);
finishTheJob(20);
finishTheJob(30);

Мы можем получить абстракции аналогичным образом.

0

Кэрри́нг — это преобразование, которое можно применить к функциям, позволяющее им принимать на одну аргумент меньше, чем ранее.

Например, в F# можно определить функцию так:

let f x y z = x + y + z

Здесь функция f принимает параметры x, y и z и суммирует их, так что:

f 1 2 3

Возвращает 6.

Согласно нашему определению, мы можем определить функцию curry для функции f следующим образом:

let curry f = fun x -> f x

Где fun x -> f x — это лямбда-функция, соответствующая x => f(x) в C#. Эта функция принимает функцию, которую вы хотите закэррить, и возвращает новую функцию, которая принимает единственный аргумент и возвращает указанную функцию с первым аргументом, установленным в значение данного аргумента.

Используя наш предыдущий пример, мы можем получить каррированную версию f так:

let curryf = curry f

Затем мы можем сделать следующее:

let f1 = curryf 1

Это даст нам функцию f1, которая эквивалентна f1 y z = 1 + y + z. Это означает, что мы можем сделать следующее:

f1 2 3

Что вернёт 6.

Этот процесс часто путают с "частичным применением функции", которое можно определить так:

let papply f x = f x

Хотя мы можем расширить это на большее количество параметров, т.е.:

let papply2 f x y = f x y
let papply3 f x y z = f x y z

Частичное применение берёт функцию и один или несколько аргументов и возвращает функцию, которая требует на один или несколько параметров меньше. Как показывают предыдущие два примера, это реализуется напрямую в стандартном определении функции F#, так что мы можем достичь предыдущего результата следующим образом:

let f1 = f 1
f1 2 3

Что вернёт 6.

В заключение:

Разница между кэррингом и частичным применением функции заключается в том, что:

Кэрринг берёт функцию и предоставляет новую функцию, принимающую единственный аргумент и возвращающую указанную функцию с первым аргументом, установленным в этот аргумент. Это позволяет нам представлять функции с несколькими параметрами как последовательность функций с единственным аргументом. Пример:

let f x y z = x + y + z
let curryf = curry f
let f1 = curryf 1
let f2 = curryf 2
f1 2 3
6
f2 1 3
6

Частичное применение функции более прямолинейно — оно берёт функцию и один или несколько аргументов и возвращает функцию с первыми n аргументами, установленными в указанные n аргументы. Пример:

let f x y z = x + y + z
let f1 = f 1
let f2 = f 2
f1 2 3
6
f2 1 3
6
0

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

0

Куррирование (currying) — это преобразование функции с N аргументами в N функций, каждая из которых принимает один аргумент. Арность (arity) функции — это количество аргументов, которое она требует.

Вот формальное определение:

curry(f) :: (a, b, c) -> f(a) -> f(b) -> f(c)

Рассмотрим реальный пример, который имеет смысл:

Предположим, вы пришли в банкомат, чтобы снять деньги. Вы вводите карту, вводите PIN-код, делаете выбор и нажимаете "ввод", чтобы подтвердить сумму вместе с запросом.

Вот обычная функция для снятия денег:

const withdraw = (cardInfo, pinNumber, request) => {
    // обрабатываем запрос
    return request.amount;
}

В этой реализации функция ожидает, что мы введем все аргументы сразу. Мы должны сначала вставить карту, ввести PIN, а затем сделать запрос, прежде чем функция будет выполнена. Если на каком-то из этих этапов возникнет ошибка, вы узнаете об этом только после ввода всех аргументов.

С помощью куррированной функции мы можем создать функции более высокой арности, чистые и простые. Чистые функции помогут нам легче отлаживать код.

Вот как будет выглядеть банкомат с куррированной функцией:

const withdraw = (cardInfo) => (pinNumber) => (request) => request.amount;

В этом случае банкомат принимает карту на вход и возвращает функцию, ожидающую PIN-код. Эта функция, в свою очередь, возвращает функцию, которая принимает объект запроса, и после успешной обработки вы получаете запрашиваемую сумму. На каждом шаге, если возникла ошибка, вы сможете легко определить, что пошло не так. Например, если вы вставили карту и получили ошибку, вы знаете, что это связано либо с картой, либо с банкоматом, но не с PIN-кодом. Если же вы ввели PIN-код, и он не был принят, вы знаете, что допустили ошибку при вводе PIN-кода. Таким образом, отладка ошибок становится более предсказуемой.

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь