Widziałem odniesienia do funkcji curry w kilku artykułach i blogach, ale nie mogę znaleźć dobrego wyjaśnienia (lub przynajmniej takiego, które ma sens!)
add x y = x+y
(curry) różni się od add (x, y)=x+y
(niezasłużonego)
Widziałem odniesienia do funkcji curry w kilku artykułach i blogach, ale nie mogę znaleźć dobrego wyjaśnienia (lub przynajmniej takiego, które ma sens!)
add x y = x+y
(curry) różni się od add (x, y)=x+y
(niezasłużonego)
Odpowiedzi:
Curry polega na rozbiciu funkcji, która bierze wiele argumentów na szereg funkcji, z których każda przyjmuje tylko jeden argument. Oto przykład w JavaScript:
function add (a, b) {
return a + b;
}
add(3, 4); // returns 7
Jest to funkcja, która pobiera dwa argumenty, aib, i zwraca ich sumę. Będziemy teraz curry tę funkcję:
function add (a) {
return function (b) {
return a + b;
}
}
Jest to funkcja, która pobiera jeden argument, a, i zwraca funkcję, która przyjmuje inny argument, b, i ta funkcja zwraca ich sumę.
add(3)(4);
var add3 = add(3);
add3(4);
Pierwsza instrukcja zwraca 7, podobnie jak instrukcja add (3, 4). Druga instrukcja definiuje nową funkcję o nazwie add3, która doda 3 do swojego argumentu. To właśnie niektórzy nazywają zamknięciem. Trzecia instrukcja używa operacji add3, aby dodać 3 do 4, ponownie wytwarzając w rezultacie 7.
[1, 2, 3, 4, 5]
, które chcesz pomnożyć przez dowolną liczbę. W Haskell mogę pisać, map (* 5) [1, 2, 3, 4, 5]
aby pomnożyć całą listę 5
, a tym samym wygenerować listę [5, 10, 15, 20, 25]
.
map
musi być funkcja, która pobiera tylko 1 argument - element z listy. Mnożenie - jako koncepcja matematyczna - jest operacją binarną; wymaga 2 argumentów. Jednak w Haskell *
jest funkcja curry, podobna do drugiej wersji add
tej odpowiedzi. Wynikiem (* 5)
jest funkcja, która pobiera pojedynczy argument i mnoży go przez 5, co pozwala nam używać go z mapą.
W algebrze funkcji radzenie sobie z funkcjami, które pobierają wiele argumentów (lub równoważny jeden argument, którym jest N-krotka) jest nieco nieeleganckie - ale, jak udowodnił Moses Schönfinkel (i, niezależnie, Haskell Curry), nie jest potrzebne: wszystko potrzebne są funkcje, które biorą jeden argument.
Jak radzisz sobie z czymś, co naturalnie wyrazisz, powiedzmy f(x,y)
,? Cóż, traktujesz to jako ekwiwalent f(x)(y)
- f(x)
, nazwij to g
, jest funkcją i zastosujesz tę funkcję do y
. Innymi słowy, masz tylko funkcje, które pobierają jeden argument - ale niektóre z nich zwracają inne funkcje (które RÓWNIEŻ biorą jeden argument ;-).
Jak zwykle, wikipedia ma ładny opis podsumowujący, zawierający wiele przydatnych wskazówek (prawdopodobnie w tym dotyczących twoich ulubionych języków ;-), a także nieco bardziej rygorystyczne podejście matematyczne.
div :: Integral a => a -> a -> a
- zwrócić uwagę na te wiele strzałek? „Mapowanie do funkcji mapowanie do a” to jedno czytanie ;-). Ty mógł używać (single) argumentu krotny dla div
& C, ale to byłoby naprawdę anty-idiomatyczne w Haskell.
Oto konkretny przykład:
Załóżmy, że masz funkcję, która oblicza siłę grawitacji działającą na obiekt. Jeśli nie znasz tej formuły, możesz ją znaleźć tutaj . Ta funkcja przyjmuje trzy niezbędne parametry jako argumenty.
Teraz, będąc na ziemi, chcesz tylko obliczyć siły dla obiektów na tej planecie. W języku funkcjonalnym możesz przekazać masę ziemi do funkcji, a następnie częściowo ją ocenić. To, co otrzymasz, to kolejna funkcja, która bierze tylko dwa argumenty i oblicza siłę grawitacji obiektów na Ziemi. Nazywa się to curry.
Curry to transformacja, którą można zastosować do funkcji, aby pozwolić im wziąć o jeden argument mniej niż poprzednio.
Na przykład w F # można zdefiniować funkcję w ten sposób:
let f x y z = x + y + z
Tutaj funkcja f bierze parametry x, y i z i sumuje je razem, więc:
f 1 2 3
Zwraca 6.
Z naszej definicji możemy zatem zdefiniować funkcję curry dla f: -
let curry f = fun x -> f x
Gdzie „fun x -> fx” jest funkcją lambda równoważną x => f (x) w C #. Ta funkcja wprowadza funkcję, którą chcesz curry i zwraca funkcję, która przyjmuje pojedynczy argument i zwraca określoną funkcję z pierwszym argumentem ustawionym na argument wejściowy.
Korzystając z naszego poprzedniego przykładu, możemy uzyskać curry f:
let curryf = curry f
Następnie możemy wykonać następujące czynności:
let f1 = curryf 1
Co daje nam funkcję f1, która jest równoważna f1 yz = 1 + y + z. Oznacza to, że możemy wykonać następujące czynności:
f1 2 3
Który zwraca 6.
Proces ten jest często mylony z „aplikacją funkcji częściowej”, którą można zdefiniować w następujący sposób:
let papply f x = f x
Chociaż możemy rozszerzyć go na więcej niż jeden parametr, tj .:
let papply2 f x y = f x y
let papply3 f x y z = f x y z
etc.
Częściowa aplikacja pobierze funkcję i parametr (y) i zwróci funkcję, która wymaga jednego lub więcej parametrów, a jak pokazują poprzednie dwa przykłady, jest zaimplementowana bezpośrednio w standardowej definicji funkcji F #, abyśmy mogli osiągnąć poprzedni wynik w ten sposób:
let f1 = f 1
f1 2 3
Co zwróci wynik 6.
Podsumowując:
Różnica między curry a aplikacją funkcji częściowej polega na tym, że:
Currying przyjmuje funkcję i udostępnia nową funkcję, która akceptuje pojedynczy argument i zwraca określoną funkcję z pierwszym argumentem ustawionym na ten argument. To pozwala nam reprezentować funkcje z wieloma parametrami jako serię funkcji pojedynczego argumentu . Przykład:-
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
Aplikacja funkcji częściowej jest bardziej bezpośrednia - pobiera funkcję i jeden lub więcej argumentów i zwraca funkcję z pierwszymi n argumentami ustawionymi na n podanych argumentów. Przykład:-
let f x y z = x + y + z
let f1 = f 1
let f2 = f 2
f1 2 3
6
f2 1 3
6
Może to być sposób używania funkcji do tworzenia innych funkcji.
W javascript:
let add = function(x){
return function(y){
return x + y
};
};
Pozwoliłby nam to tak nazwać:
let addTen = add(10);
Kiedy to działa, 10
jest przekazywane jako x
;
let add = function(10){
return function(y){
return 10 + y
};
};
co oznacza, że zwrócono nam tę funkcję:
function(y) { return 10 + y };
Więc kiedy zadzwonisz
addTen();
naprawdę dzwonisz:
function(y) { return 10 + y };
Więc jeśli to zrobisz:
addTen(4)
jest taki sam jak:
function(4) { return 10 + 4} // 14
Dlatego addTen()
zawsze dodajemy dziesięć do wszystkiego, co przekazujemy. Możemy wykonywać podobne funkcje w ten sam sposób:
let addTwo = add(2) // addTwo(); will add two to whatever you pass in
let addSeventy = add(70) // ... and so on...
Teraz oczywistym pytaniem jest, dlaczego, u licha, chciałbyś to zrobić? Zmienia to, co było chętną operacją, x + y
w tę, którą można leniwie przejść, co oznacza, że możemy zrobić co najmniej dwie rzeczy: 1. buforować kosztowne operacje 2. osiągnąć abstrakcje w paradygmacie funkcjonalnym.
Wyobraź sobie, że nasza funkcja curry wyglądała tak:
let doTheHardStuff = function(x) {
let z = doSomethingComputationallyExpensive(x)
return function (y){
z + y
}
}
Możemy wywołać tę funkcję jeden raz, a następnie przekazać wyniki, aby użyć ich w wielu miejscach, co oznacza, że wykonujemy drogie obliczeniowo tylko raz:
let finishTheJob = doTheHardStuff(10)
finishTheJob(20)
finishTheJob(30)
Możemy uzyskać abstrakcje w podobny sposób.
Funkcja curry jest funkcją kilku przerobionych argumentów, tak że akceptuje pierwszy argument i zwraca funkcję, która akceptuje drugi argument i tak dalej. Pozwala to funkcjom kilku argumentów częściowo zastosować niektóre z ich początkowych argumentów.
map
funkcji f
na liście list, xss
możesz to zrobić map (map f) xss
.
Oto zabawkowy przykład w Pythonie:
>>> 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..."
(Wystarczy użyć konkatenacji za pomocą +, aby uniknąć rozproszenia uwagi dla programistów niebędących Pythonami).
Edycja do dodania:
Zobacz http://docs.python.org/library/functools.html?highlight=partial#functools.partial , który pokazuje także częściowe rozróżnienie obiektu na funkcję w sposób, w jaki Python implementuje to.
Curry tłumaczy funkcję z wywoływalnej f(a, b, c)
na wywoływalną jako f(a)(b)(c)
.
W przeciwnym razie curry ma miejsce wtedy, gdy rozkładasz funkcję, która bierze wiele argumentów w szereg funkcji, które biorą udział w argumentach.
Dosłownie curry to transformacja funkcji: z jednego sposobu wywoływania w inny. W JavaScript zwykle tworzymy opakowanie, aby zachować oryginalną funkcję.
Curry nie wywołuje funkcji. Po prostu to przekształca.
Zróbmy funkcję curry, która wykonuje curry dla funkcji dwuargumentowych. Innymi słowy, curry(f)
dla dwóch argumentów f(a, b)
tłumaczy to naf(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
Jak widać, implementacja jest serią opakowań.
curry(func)
jest opakowanie function(a)
.sum(1)
argument jest zapisywany w środowisku leksykalnym i zwracane jest nowe opakowanie function(b)
.sum(1)(2)
końcu wywołuje function(b)
podając 2 i przekazuje wywołanie do oryginalnej sumy wielu argumentów.Jeśli rozumiesz, partial
że jesteś w połowie drogi. Chodzi o partial
to, aby wstępnie zastosować argumenty do funkcji i zwrócić nową funkcję, która chce tylko pozostałych argumentów. Po wywołaniu tej nowej funkcji zawiera ona wstępnie załadowane argumenty wraz z wszelkimi dostarczonymi argumentami.
W Clojure +
jest funkcja, ale aby wyjaśnić wszystko wyraźnie:
(defn add [a b] (+ a b))
Możesz być świadomy, że inc
funkcja po prostu dodaje 1 do dowolnej liczby, którą przekazuje.
(inc 7) # => 8
Zbudujmy go sami, używając partial
:
(def inc (partial add 1))
Tutaj zwracamy inną funkcję, która ma 1 załadowany do pierwszego argumentu add
. Ponieważ add
przyjmuje dwa argumenty, nowa inc
funkcja chce tylko b
argumentu, a nie 2 argumentów, jak poprzednio, ponieważ 1 został już częściowo zastosowany. partial
Jest to zatem narzędzie, za pomocą którego można tworzyć nowe funkcje z domyślnymi wartościami domyślnymi. Dlatego w funkcjonalnym języku funkcje często porządkują argumenty od ogólnych do szczegółowych. Ułatwia to ponowne użycie takich funkcji do zbudowania innych funkcji.
Teraz wyobraź sobie, że język był wystarczająco inteligentny, aby zrozumieć introspekcyjnie, add
wymagając dwóch argumentów. Kiedy przekazaliśmy mu jeden argument, zamiast się wahać, co jeśli funkcja częściowo zastosowała argument, przekazaliśmy go w naszym imieniu, rozumiejąc, że prawdopodobnie zamierzamy podać drugi argument później? Możemy wtedy zdefiniować inc
bez wyraźnego użycia partial
.
(def inc (add 1)) #partial is implied
Tak zachowują się niektóre języki. Jest to wyjątkowo przydatne, gdy chce się łączyć funkcje w większe transformacje. Doprowadziłoby to do przetworników.
Znalazłem ten artykuł i artykuł, do którego się odwołuje, użyteczne, aby lepiej zrozumieć curry: http://blogs.msdn.com/wesdyer/archive/2007/01/29/currying-and-partial-function-application.aspx
Jak wspomniano inni, jest to tylko sposób na posiadanie funkcji jednego parametru.
Jest to przydatne, ponieważ nie musisz zakładać, ile parametrów zostanie przekazanych, więc nie potrzebujesz funkcji 2 parametrów, 3 parametrów i 4 parametrów.
Jak wszystkie inne odpowiedzi curry pomaga tworzyć częściowo zastosowane funkcje. Javascript nie zapewnia natywnej obsługi automatycznego curry. Przykłady podane powyżej mogą nie pomóc w praktycznym kodowaniu. Istnieje doskonały przykład w livecript (który zasadniczo kompiluje się do js) http://livescript.net/
times = (x, y) --> x * y
times 2, 3 #=> 6 (normal use works as expected)
double = times 2
double 5 #=> 10
W powyższym przykładzie, gdy podałeś mniej argumentów, skrypt życia generuje dla ciebie nową funkcję curry (podwójnie)
Curry może uprościć kod. Jest to jeden z głównych powodów, aby z tego korzystać. Curry to proces przekształcania funkcji, która akceptuje n argumentów w n funkcji, które akceptują tylko jeden argument.
Zasadą jest przekazywanie argumentów przekazanej funkcji za pomocą właściwości closure (closure), aby przechowywać je w innej funkcji i traktować jako wartość zwracaną, a funkcje te tworzą łańcuch, a końcowe argumenty są przekazywane do uzupełnienia operacja.
Zaletą tego jest to, że może uprościć przetwarzanie parametrów, radząc sobie z jednym parametrem na raz, co może również poprawić elastyczność i czytelność programu. Dzięki temu program jest łatwiejszy w zarządzaniu. Również podzielenie kodu na mniejsze części sprawi, że będzie on łatwy do ponownego użycia.
Na przykład:
function curryMinus(x)
{
return function(y)
{
return x - y;
}
}
var minus5 = curryMinus(1);
minus5(3);
minus5(5);
Mogę też zrobić ...
var minus7 = curryMinus(7);
minus7(3);
minus7(5);
Jest to bardzo świetne do porządkowania złożonego kodu i obsługi niezsynchronizowanych metod itp.
Funkcja curry jest stosowana do wielu list argumentów zamiast tylko jednej.
Oto zwykła funkcja bez curry, która dodaje dwa parametry Int, xiy:
scala> def plainOldSum(x: Int, y: Int) = x + y
plainOldSum: (x: Int,y: Int)Int
scala> plainOldSum(1, 2)
res4: Int = 3
Oto podobna funkcja, która jest curry. Zamiast jednej listy dwóch parametrów Int zastosujesz tę funkcję do dwóch list jednego parametru Int każdy:
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
To, co się tutaj dzieje, polega na tym, że kiedy wywołujesz curriedSum
, faktycznie otrzymujesz dwie tradycyjne wywołania funkcji z powrotem do tyłu. Pierwsze wywołanie funkcji przyjmuje pojedynczy parametr Int o nazwie x
i zwraca wartość funkcji dla drugiej funkcji. Ta druga funkcja przyjmuje parametr Int
y
.
Oto funkcja o nazwie first
, która w duchu robi to, co curriedSum
zrobiłaby pierwsza tradycyjna funkcja :
scala> def first(x: Int) = (y: Int) => x + y
first: (x: Int)(Int) => Int
Zastosowanie 1 do pierwszej funkcji - innymi słowy, wywołanie pierwszej funkcji i przekazanie jej 1 - daje drugą funkcję:
scala> val second = first(1)
second: (Int) => Int = <function1>
Zastosowanie 2 do drugiej funkcji daje wynik:
scala> second(2)
res6: Int = 3
Przykładem curry może być posiadanie funkcji, które znasz tylko jeden z parametrów:
Na przykład:
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
}
Tutaj, ponieważ nie znasz drugiego parametru dla wywołania zwrotnego podczas wysyłania go performAsyncRequest(_:)
, musisz utworzyć kolejną lambdę / zamknięcie, aby wysłać ten parametr do funkcji.
func callback
powrót się? Nazywa się to @, callback(str)
więc let callback = callback(str)
oddzwanianie jest tylko wartością func callback
func callback(_:data:)
akceptuje dwa parametry, tutaj podaję tylko jeden, String
więc czeka na następny ( NSData
), dlatego teraz let callback
jest kolejna funkcja czekająca na przesłanie danych
Oto przykład ogólnej i najkrótszej wersji funkcji curry z nr n. params.
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)());
Tutaj możesz znaleźć proste wyjaśnienie implementacji curry w C #. W komentarzach starałem się pokazać, jak curry może być przydatne:
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);
Curry to jedna z wyższych funkcji Java Script.
Curry jest funkcją wielu argumentów, która jest przepisywana w taki sposób, że pobiera pierwszy argument i zwraca funkcję, która z kolei używa pozostałych argumentów i zwraca wartość.
Zmieszany?
Zobaczmy przykład
function add(a,b)
{
return a+b;
}
add(5,6);
Jest to podobne do następującej funkcji curry,
function add(a)
{
return function(b){
return a+b;
}
}
var curryAdd = add(5);
curryAdd(6);
Co oznacza ten kod?
Teraz przeczytaj ponownie definicję,
Curry jest funkcją wielu argumentów, która jest przepisywana tak, że pobiera pierwszy argument i zwraca funkcję, która z kolei wykorzystuje pozostałe argumenty i zwraca wartość.
Wciąż zmieszany? Pozwól mi wyjaśnić głęboko!
Po wywołaniu tej funkcji
var curryAdd = add(5);
Zwróci ci taką funkcję,
curryAdd=function(y){return 5+y;}
Nazywa się to funkcjami wyższego rzędu. Oznacza to, że wywoływanie jednej funkcji po kolei zwraca inną funkcję jest dokładną definicją funkcji wyższego rzędu. Jest to największa zaleta legendy Java Script. Wróć więc do curry
Ten wiersz przekaże drugi argument do funkcji curryAdd.
curryAdd(6);
co z kolei powoduje,
curryAdd=function(6){return 5+6;}
// Which results in 11
Mam nadzieję, że rozumiesz użycie curry tutaj. Wracając do zalet
Dlaczego curry?
Wykorzystuje możliwość ponownego użycia kodu. Mniej kodu, mniej błędów. Możesz zapytać, jak to jest mniej kodu?
Mogę to udowodnić za pomocą skryptu ECMA 6 nowych funkcji strzałek funkcji.
Tak! ECMA 6, zapewnij nam wspaniałą funkcję zwaną funkcjami strzałek,
function add(a)
{
return function(b){
return a+b;
}
}
Za pomocą funkcji strzałki możemy napisać powyższą funkcję w następujący sposób,
x=>y=>x+y
Fajnie prawda?
Więc mniej kodu i mniej błędów !!
Za pomocą funkcji wyższego rzędu można łatwo opracować kod wolny od błędów.
Wyzywam cię!
Mam nadzieję, że zrozumiałeś, co jest curry. Skomentuj tutaj, jeśli potrzebujesz wyjaśnień.
Dzięki. Miłego dnia!
Istnieje przykład „Currying in ReasonML”.
let run = () => {
Js.log("Curryed function: ");
let sum = (x, y) => x + y;
Printf.printf("sum(2, 3) : %d\n", sum(2, 3));
let per2 = sum(2);
Printf.printf("per2(3) : %d\n", per2(3));
};
curry
iuncurry
funkcjami Haskella. Ważne jest tutaj to, że te izomorfizmy są wcześniej ustalone, a zatem „wbudowane” w język.