Что такое «Currying»?

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

Currying – это когда вы разбиваете функцию, которая принимает несколько аргументов в ряд функций, которые принимают участие в аргументах. Вот пример в 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.

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

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

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

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

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

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

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

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

 let fxyz = x + y + z 

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

 f 1 2 3 

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

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

 let curry f = fun x -> fx 

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

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

 let curryf = curry f 

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

 let f1 = curryf 1 

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

 f1 2 3 

Который возвращает 6.

Этот процесс часто путают с «приложением частичной функции», которое можно определить таким образом:

 let papply fx = fx 

Хотя мы можем расширить его до более чем одного параметра, то есть: –

 let papply2 fxy = fxy let papply3 fxyz = fxyz etc. 

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

 let f1 = f 1 f1 2 3 

Который вернет результат 6.

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

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

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

 let fxyz = 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 fxyz = x + y + z let f1 = f 1 let f2 = f 2 f1 2 3 6 f2 1 3 6 

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

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

В 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) { 10 + y }; 

Поэтому, когда вы звоните

  addTen(); 

вы действительно звоните:

  function(y) { 10 + y }; 

Поэтому, если вы это сделаете:

  addTen(4) 

это то же самое, что:

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

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

 let addTwo = add(2) // addTwo(); will add two to whatever you pass in let addSeventy = add(70) // ... and so on... 

Вот пример игрушки в 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's 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.

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

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

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

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

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

 (defn add [ab] (+ ab)) 

Вы можете знать, что функция 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 

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

Функция 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 

Вот аналогичная функция, которая находится в карри. Вместо одного списка из двух параметров 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 , вы фактически получаете две традиционные вызовы функций назад. Первый вызов функции принимает один параметр Int с именем x и возвращает значение функции для второй функции. Эта вторая функция принимает параметр Int y .

Вот функция, названная first , которая в духе делает то, что curriedSum бы первый вызов традиционной функции 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 =  

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

 scala> second(2) res6: Int = 3 

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

Например:

 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(_:) вам нужно будет создать еще один lambda / замыкание, чтобы отправить его в функцию.

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

  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 – это валютная функция, которая возвращает 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 и т. Д.)

Поскольку все другие ответы 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)

Карри может упростить ваш код. Это одна из основных причин этого. 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); 

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

Interesting Posts

Почему я получаю значок предупреждения, когда добавляю ссылку на проект плагина MEF?

программно добавлять столбцы и строки в WPF Datagrid

Создание шаблона / стойкого шаблона заголовка / нижнего колонтитула в jQuery Mobile и PhoneGap

JQuery UI tooltip не поддерживает html-контент

Аргументы process.start ()

Множество TypeFace в одиночном TextView

Различные зависимости для разных профилей сборки

Поддержка материнской платы LGA775

Увеличить пространство пермгенов

Строка состояния становится белой и не отображает содержимое за ней

R: Построение 3D-поверхности из x, y, z

Почему новые методы java.util.Arrays в Java 8 не перегружены для всех примитивных типов?

Как смотреть презентацию Lync 2013 в полноэкранном режиме?

SSH по-прежнему запрашивает пароль после настройки проверки подлинности на основе ключа

Настройка автоматической автоподстройки фокуса и автоматической задержки автозапуска в Windows 7

Давайте будем гением компьютера.