Что такое "Currying"?

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

Ответ 1

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

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

add(3, 4); // returns 7

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

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

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

add(3)(4);

var add3 = add(3);

add3(4);

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

Ответ 2

В алгебре функций, относящихся к функциям, которые принимают несколько аргументов (или эквивалентный один аргумент, что N-кортеж) несколько неэлегантно, но, как доказал Моисей Шонфинкель (и, независимо, Хаскелл Карри), он не нужен: все, что вам нужно, это функции, которые принимают один аргумент.

Итак, как вы справляетесь с чем-то, что вы, естественно, выражаете, скажем, f(x,y)? Ну, вы принимаете это как эквивалент f(x)(y) - f(x), называете его g, является функцией, и вы применяете эту функцию к y. Другими словами, у вас есть только функции, которые принимают один аргумент, но некоторые из этих функций возвращают другие функции (которые также принимают один аргумент; -).

Как обычно, wikipedia имеет приятную сводную запись об этом, с множеством полезных указателей (возможно, включая те, которые относятся к вашим любимым языкам;-), а также слегка более строгая математическая обработка.

Ответ 3

Вот конкретный пример:

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

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

Ответ 4

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

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

let f x y z = x + y + z

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

f 1 2 3

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

Поэтому из нашего определения мы можем определить функцию карри для f: -

let curry f = fun x -> f x

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

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

let curryf = curry f

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

let f1 = curryf 1

Который предоставляет нам функцию f1, которая эквивалентна f1 yz = 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
etc.

Частичное приложение будет принимать функцию и параметр и возвращать функцию, которая требует одного или нескольких меньших параметров, и, как показывают предыдущие два примера, реализована непосредственно в стандартном определении функции 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

Ответ 5

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

В 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(); will add two to whatever you pass in
let addSeventy = add(70)  // ... and so on...

Ответ 6

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

Ответ 7

Вот игрушечный пример на Python:

>>> from functools import partial as curry

>>> # Original function taking three parameters:
>>> def display_quote(who, subject, quote):
        print who, 'said regarding', subject + ':'
        print '"' + quote + '"'


>>> display_quote("hoohoo", "functional languages",
           "I like Erlang, not sure yet about Haskell.")
hoohoo said regarding functional languages:
"I like Erlang, not sure yet about Haskell."

>>> # Let curry the function to get another that always quotes Alex...
>>> am_quote = curry(display_quote, "Alex Martelli")

>>> am_quote("currying", "As usual, wikipedia has a nice summary...")
Alex Martelli said regarding currying:
"As usual, wikipedia has a nice summary..."

(Просто используйте конкатенацию через +, чтобы не отвлекаться на не-Python программистов.)

Редактирование добавить:

См. Http://docs.python.org/library/functools.html?highlight=partial#functools.partial, где также показано различие между частичным объектом и функцией в том, как Python реализует это.

Ответ 8

Curry переводит функцию из вызываемой как f(a, b, c) в вызываемую как f(a)(b)(c).

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

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

Карринг не вызывает функцию. Это просто трансформирует его.

Давайте сделаем функцию карри, которая выполняет каррирование для функций с двумя аргументами. Другими словами, curry(f) для двух аргументов f(a, b) переводит его в f(a)(b)

function curry(f) { // curry(f) does the currying transform
  return function(a) {
    return function(b) {
      return f(a, b);
    };
  };
}

// usage
function sum(a, b) {
  return a + b;
}

let carriedSum = curry(sum);

alert( carriedSum(1)(2) ); // 3

Как видите, реализация представляет собой серию оболочек.

  • Результатом curry(func) является function(a) обертка function(a).
  • Когда он вызывается как sum(1), аргумент сохраняется в лексической среде, и возвращается новая оболочка function(b).
  • Затем sum(1)(2) наконец, вызывает function(b) обеспечивающую 2, и передает вызов исходной сумме с несколькими аргументами.

Ответ 9

Если вы понимаете partial, вы на полпути. Идея partial состоит в том, чтобы предварять аргументы функции и возвращать новую функцию, которая хочет только остальные аргументы. Когда эта новая функция называется, она включает в себя предустановленные аргументы вместе с любыми аргументами, которые были ему предоставлены.

В Clojure + есть функция, но для того, чтобы сделать вещи ясными:

(defn add [a b] (+ a b))

Вы можете знать, что функция inc просто добавляет 1 к любому количеству, которое оно передало.

(inc 7) # => 8

Построим его самостоятельно, используя partial:

(def inc (partial add 1))

Здесь мы возвращаем другую функцию, которая имеет 1 загруженный в первый аргумент add. Поскольку add принимает два аргумента, новая функция inc хочет только аргумент b, а не 2 аргумента, как и раньше, поскольку 1 уже частично применяется. Таким образом, partial - это инструмент, с помощью которого можно создавать новые функции со значениями по умолчанию, которые предполагается использовать. Вот почему в функциональном языке функции часто упорядочивают аргументы от общего к конкретному. Это облегчает повторное использование таких функций, из которых можно построить другие функции.

Теперь представьте, был ли язык достаточно умен, чтобы понять интроспективно, что add хотел два аргумента. Когда мы передали ему один аргумент, а не отклоняли, что, если функция частично применила аргумент, мы передали его от нашего имени, понимая, что мы, вероятно, хотели бы предоставить другой аргумент позже? Затем мы могли бы определить inc без явного использования partial.

(def inc (add 1)) #partial is implied

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

Ответ 10

Я нашел эту статью, и статья, в которой он ссылается, полезен, чтобы лучше понять каррирование: http://blogs.msdn.com/wesdyer/archive/2007/01/29/currying-and-partial-function-application.aspx

Как упоминалось выше, это всего лишь способ иметь одну функцию параметра.

Это полезно в том, что вам не нужно предполагать, сколько параметров будет передано, поэтому вам не нужны параметры 2, 3 параметра и 4 параметра.

Ответ 11

Поскольку все другие ответы currying помогают создавать частично прикладные функции. Javascript не предоставляет встроенную поддержку автоматического currying. Таким образом, приведенные выше примеры могут не помочь в практическом кодировании. Существует несколько отличных примеров в livescript (который по существу компилируется в js) http://livescript.net/

times = (x, y) --> x * y
times 2, 3       #=> 6 (normal use works as expected)
double = times 2
double 5         #=> 10

В приведенном выше примере, когда вы указали меньше аргументов, сценарий livescript генерирует для вас новую валютную функцию (double)

Ответ 12

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

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

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

Например:

function curryMinus(x) 
{
  return function(y) 
  {
    return x - y;
  }
}

var minus5 = curryMinus(1);
minus5(3);
minus5(5);

Я тоже могу...

var minus7 = curryMinus(7);
minus7(3);
minus7(5);

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

Ответ 13

Функция curried применяется к нескольким спискам аргументов, а не просто один.

Вот регулярная, не-каррическая функция, которая добавляет два Int параметры, x и y:

scala> def plainOldSum(x: Int, y: Int) = x + y
plainOldSum: (x: Int,y: Int)Int
scala> plainOldSum(1, 2)
res4: Int = 3

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

scala> def curriedSum(x: Int)(y: Int) = x + y
curriedSum: (x: Int)(y: Int)Intscala> second(2)
res6: Int = 3
scala> curriedSum(1)(2)
res5: Int = 3

Что происходит, так это то, что при вызове curriedSum вы фактически получаете две традиционные вызовы функций назад. Первая функция invocation принимает один параметр Int с именем x и возвращает функцию значение для второй функции. Эта вторая функция принимает параметр Int y.

Вот функция с именем first, которая делает в духе то, что первый традиционный вызов функции curriedSum:

scala> def first(x: Int) = (y: Int) => x + y
first: (x: Int)(Int) => Int

Применение 1 к первой функции - другими словами, вызов первой функции и передавая в 1-й степени вторую функцию:

scala> val second = first(1)
second: (Int) => Int = <function1>

Применяя 2 ко второй функции, получаем результат:

scala> second(2)
res6: Int = 3

Ответ 14

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

Например:

func aFunction(str: String) {
    let callback = callback(str) // signature now is `NSData -> ()`
    performAsyncRequest(callback)
}

func callback(str: String, data: NSData) {
    // Callback code
}

func performAsyncRequest(callback: NSData -> ()) {
    // Async code that will call callback with NSData as parameter
}

Здесь, поскольку вы не знаете второй параметр для обратного вызова при отправке его на performAsyncRequest(_:), вам нужно будет создать еще один лямбда/замыкание, чтобы отправить его в функцию.

Ответ 15

Здесь вы можете найти простое объяснение реализации currying в С#. В комментариях я попытался показать, как каррирование может быть полезным:

public static class FuncExtensions {
    public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult> func)
    {
        return x1 => x2 => func(x1, x2);
    }
}

//Usage
var add = new Func<int, int, int>((x, y) => x + y).Curry();
var func = add(1);

//Obtaining the next parameter here, calling later the func with next parameter.
//Or you can prepare some base calculations at the previous step and then
//use the result of those calculations when calling the func multiple times 
//with different input parameters.

int result = func(1);

Ответ 16

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

Типичная рекурсия - внизу у меня есть функция, которая возвращает функцию (сама в этом случае), которая возвращает функцию, которая есть... если не выполняется какое-либо условие или мой компьютер взорван, если я написал это неправильно.

fact = (n) => n == 0 ? 1 : n*fact(n-1);

отсюда Так что каррирование происходит со мной так же, как вручную писать каждый шаг рекурсии. Возможно, это имя от этого факта - re + CURR + sion → CURR + ying. Я не знаю, просто догадываюсь.

Ответ 17

Чтобы дать реальный мир (и, возможно, полезный) пример currying, проверьте, как вы можете выполнять вызовы сервера в javascript с помощью библиотеки fetch

    Get(url) {
        let fullUrl = toFullUrl(url);

        let promise = getPromiseForFetchWithToken((token) => {

        let headers = Object.assign(
            getDefaultHeaders(token),
            jsonHeaders);

        let config = {
            method: "GET",
            headers: headers
        };

        return fetch(fullUrl, config);
    });

    return promise;
}

Где getPromiseForFetchWithToken - это функция в карри, которая возвращает a Promise с результатом выборки, показанной ниже:

function getPromiseForFetchWithToken(tokenConsumingFetch) {
   function resolver(resolve, reject) {

    let token = localStorage.getItem("token");

    tokenConsumingFetch(token)
        .then(checkForError)
        .then((response) => {
            if (response) resolve(response);
        })
        .catch(reject);
   }

   var promise = new Promise(resolver);

   return promise;
}

Это позволяет вам ждать вызова функции Get, а затем надлежащим образом обрабатывать возвращаемое значение независимо от того, что это такое, вы можете повторно использовать функцию getPromiseForFetchWithToken в любом месте, где требуется сделать серверный вызов, который должен включают токен-носитель. (Put, Delete, Post и т.д.)

Ответ 18

Вот пример универсальной и самой короткой версии для функции карри при n нет. парам.

const add = a => b => b ? add(a + b) : a; 

const add = a => b => b ? add(a + b) : a; 
console.log(add(1)(2)(3)(4)());

Ответ 19

Еще один простой и понятный пример функции currying в JavaScript.

Предположим, что мы имеем функцию с несколькими параметрами. Например, функция вычисления объема некоторых ящиков. Требуется 3 параметра: длина, ширина и высота.

function volume(l, w, h) {    
  return l * w * h
}

Теперь предположим, что нам нужно написать код для вычисления объема для 10 ящиков, и все эти поля имеют одинаковую ширину и длину. Например: 5 x 2. Но все они имеют разную высоту. (Таким образом, только высота является изменяемым параметром). Такой случай является прекрасной причиной для использования карри.

function curry(f, ...args) {    
  return (..._innerArgs) => {        
    return f(...args, ..._innerArgs);    
  }
}

Используя эту функцию, мы можем теперь создать каррину тома функции, чтобы передать ей только 1 переменный аргумент (высота) в будущем. Итак, дайте curry объемную функцию по двум первым аргументам (длина и вес).

const curriedVolume2 = curry(volume, 5, 2);

Теперь, имея функцию curriedVolume2, чтобы рассчитать объем ящика с высотой 10, нам просто нужно написать

console.log( curriedVolume2(10) );     // >> 100

Для высоты 15,16,17...:

console.log( curriedVolume2(15) );     // >> 150
console.log( curriedVolume2(16) );     // >> 160
console.log( curriedVolume2(17) );     // >> 170

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

const curriedVolume = curry(volume, 2);
console.log( curriedVolume(3, 4) );    // >> 24
console.log( curriedVolume(5, 20) );   // >> 200

Вы можете спросить: "Но зачем это? Для оптимизации расчетов?"

Нет. Currying - чрезвычайно полезный метод в функциональном JavaScript. Он позволяет создавать библиотеку небольших, легко настраиваемых функций с последовательным поведением, которые быстро используются и могут быть легко поняты при чтении кода.

Ответ 20

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