2008-08-30 34 views
441

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

+6

[Оставленный как комментарий, поскольку он будет бесполезен для нематематиков.] Согласно определению декартовой замкнутой категории существует * фиксированное * семейство дополнений (естественно параметризованный A) между X -> X x A и X -> X^A. Изоморфизмы hom (X x A, Y) <-> hom (X, Y^A) являются функциями «curry» и «uncurry» Haskell. Здесь важно, что эти изоморфизмы фиксируются заранее и, следовательно, «встроены» в язык. – 2011-07-11 15:23:23

+1

Здесь есть хороший учебник для currying in haskell. Http://learnyouahaskell.com/higher-order-functions#curried-functions. Короткие комментарии: `add xy = x + y` (curried) отличается от` add (x , y) = x + y` (uncurried) – Jaider 2012-08-20 18:08:18

ответ

549

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

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

add(3, 4); // returns 7 

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

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

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

add(3)(4); 

var add3 = add(3); 

add3(4); 

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

+139

В практическом смысле, как я могу использовать эту концепцию? – Strawberry 2013-08-08 18:00:42

+20

@Strawberry, скажем, например, что у вас есть список чисел в `[1, 2, 3, 4, 5]`, которые вы хотите умножить на произвольное число. В Haskell я могу написать `map (* 5) [1, 2, 3, 4, 5]`, чтобы умножить весь список на `5` и, таким образом, сформировать список` [5, 10, 15, 20, 25 ] `. – nyson 2013-10-26 16:52:04

+38

Я понимаю, что делает функция карты, но я не уверен, понимаю ли я то, что вы пытаетесь мне проиллюстрировать. Вы говорите, что функция карты представляет собой концепцию каррирования? – Strawberry 2013-10-26 23:11:24

42

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

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

let f x y z = x + y + z 

Здесь функция F принимает параметры х, у и г и суммирует их вместе так: -

f 1 2 3 

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

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

let curry f = fun x -> f x 

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

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

let curryf = curry f 

Теперь мы можем сделать следующее: -

let f1 = curryf 1 

Который дает нам функции f1, которая equivilent к 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.

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

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

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

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 

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

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

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

94

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

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

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

3

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

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

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

71

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

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

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

5

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

(Только с помощью конкатенации с помощью +, чтобы избежать отвлечения для программистов непитоновские.)

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

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

2

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

Здесь регулярно, не кэрри функция, которая добавляет два Int параметры х и у:

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 бы:

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

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

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

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

scala> second(2) 
res6: Int = 3 
3

Если вы понимаете, что 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 

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

19

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

В 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... 
2

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

Например:

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

0

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

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

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

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

0

Чтобы дать реальный мир (и потенциально полезным) пример выделки проверить, как вы можете совершать звонки сервера в 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 и затем соответствующим образом обрабатывать возвращаемое значение независимо от того, что это такое, вы ча n повторно использовать функцию getPromiseForFetchWithToken в любом месте, где вам нужно сделать вызов сервера, который должен включать токен-носитель. (Put, Delete, Post и т. Д.)