Jaka jest różnica między currying i częściowe zastosowanie?

Dość często widzę w Internecie różne skargi, że inne narody przykłady currying nie są currying, ale w rzeczywistości są tylko częściowe zastosowanie.

Nie znalazłem porządnego wyjaśnienia, czym jest aplikacja częściowa, ani czym różni się od curryingu. Wydaje się, że istnieje ogólne zamieszanie, z równoważnymi przykładami opisywanymi jako currying w niektórych miejscach, a częściowe zastosowanie w innych.

Mógłby mi ktoś podać definicję obu terminów i szczegóły czym się różnią?

Author: ArtB, 2008-10-20

13 answers

Currying jest konwersją pojedynczej funkcji N argumentów naN funkcje z pojedynczym argumentem każda. Podano następującą funkcję:

function f(x,y,z) { z(x(y));}

Gdy Curry, staje się:

function f(x) { lambda(y) { lambda(z) { z(x(y)); } } }

Aby uzyskać pełną aplikację f (x, y,z), musisz zrobić to:

f(x)(y)(z);

Wiele języków funkcyjnych pozwala pisać f x y z. Jeśli wywołasz tylko f x y lub f(x)(y) wtedy otrzymujesz częściowo zastosowaną funkcję-wartość zwracana jest zamknięciem lambda(z){z(x(y))} z przekazywane-w wartościach x i y do f(x,y).

Jednym ze sposobów użycia aplikacji częściowej jest definiowanie funkcji jako częściowych zastosowań funkcji uogólnionych, np. fold :

function fold(combineFunction, accumalator, list) {/* ... */}
function sum     = curry(fold)(lambda(accum,e){e+accum}))(0);
function length  = curry(fold)(lambda(accum,_){1+accum})(empty-list);
function reverse = curry(fold)(lambda(accum,e){concat(e,accum)})(empty-list);

/* ... */
@list = [1, 2, 3, 4]
sum(list) //returns 10
@f = fold(lambda(accum,e){e+accum}) //f = lambda(accumaltor,list) {/*...*/}
f(0,list) //returns 10
@g = f(0) //same as sum
g(list)  //returns 10
 213
Author: Mark Cidade,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-20 11:25:10

Najprostszym sposobem, aby zobaczyć, jak różnią się one, jest rozważenie prawdziwego przykładu. Załóżmy, że mamy funkcję Add, która przyjmuje 2 liczby jako wejście i zwraca liczbę jako wyjście, np. Add(7, 5) zwraca 12. W tym przypadku:

  • Częściowe zastosowanie Funkcja Add z wartością 7 da nam nową funkcję jako wyjście. Ta funkcja sama przyjmuje 1 liczbę jako wejście i wyprowadza liczbę. Jako takie:

    Partial(Add, 7); // returns a function f2 as output
    
                     // f2 takes 1 number as input and returns a number as output
    

    Więc możemy zrobić to:

    f2 = Partial(Add, 7);
    f2(5); // returns 12;
           // f2(7)(5) is just a syntactic shortcut
    
  • Currying Funkcja Add da nam nową funkcję jako wyjście. Ta funkcja sama przyjmuje 1 liczbę jako wejście i wyjście , Ale kolejna nowa funkcja. Ta trzecia funkcja przyjmuje 1 liczbę jako wejście i zwraca liczbę jako wyjście. Jako takie:

    Curry(Add); // returns a function f2 as output
    
                // f2 takes 1 number as input and returns a function f3 as output
                // i.e. f2(number) = f3
    
                // f3 takes 1 number as input and returns a number as output
                // i.e. f3(number) = number
    

    Więc możemy to zrobić:

    f2 = Curry(Add);
    f3 = f2(7);
    f3(5); // returns 12
    
Innymi słowy, "currying" i "częściowe zastosowanie" to dwie zupełnie różne funkcje. Currying zajmuje dokładnie 1 input, podczas gdy aplikacja częściowa pobiera 2 (lub więcej) wejścia.

Mimo że obie zwracają funkcję jako wyjście, zwracane funkcje mają zupełnie inne formy, jak pokazano powyżej.

 131
Author: Pacerier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-22 14:35:19

Uwaga: zostało to zaczerpnięte z F # Basics doskonałego artykułu wprowadzającego dla programistów.NET do programowania funkcyjnego.

Currying oznacza rozbicie funkcji z wieloma argumentami na szereg funkcji, z których każdy przyjmuje jeden argument i ostatecznie tworzy taki sam wynik jak oryginalna funkcja. Currying jest chyba najbardziej trudny temat dla programistów początkujących w programowaniu funkcyjnym, szczególnie dlatego, że jest często mylony z częściowym podanie. Możesz zobaczyć oba w pracy w tym przykładzie:

let multiply x y = x * y    
let double = multiply 2
let ten = double 5

Od razu powinieneś zobaczyć zachowanie, które różni się od większości języki imperatywne. Drugie polecenie tworzy nową funkcję nazywa się podwójną przez przekazanie jednego argumentu funkcji, która zajmuje dwa. Wynikiem jest funkcja, która przyjmuje jeden argument int i daje to samo wyjście, jak gdybyś wywołał mnożenie przez X równe 2 i y równy temu argumentowi. Jeśli chodzi o zachowanie, to tak samo jak to kod:

let double2 z = multiply 2 z

Często ludzie błędnie mówią, że mnożenie jest zwijane, aby utworzyć podwójne. Ale jest to tylko w pewnym sensie prawda. Funkcja mnożenia jest curry, ale dzieje się tak, gdy jest zdefiniowana, ponieważ funkcje W F# są default. Po utworzeniu podwójnej funkcji jest ona dokładniejsza do powiedzmy, że funkcja mnożenia jest częściowo zastosowana.

Funkcja mnożenia jest w rzeczywistości szeregiem dwóch funkcji. Pierwszy funkcja pobiera jeden argument int i zwraca inna funkcja, efektywnie Wiązanie x z określoną wartością. Funkcja ta przyjmuje również argument int, który można traktować jako wartość do powiązania z y. po wywołanie tej drugiej funkcji, x i y są powiązane, więc wynik jest iloczyn x i y zdefiniowany w ciele dwójkowym.

Aby utworzyć podwójną, pierwszą funkcję w łańcuchu mnożenia funkcje są oceniane jako częściowo stosowane mnożenie. W wyniku funkcja otrzymuje nazwę double. Gdy podwójne jest oceniany, wykorzystuje jego argument wraz z częściowo zastosowaną wartością do utworzenia wynik.

 47
Author: dodgy_coder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-04 05:18:33

Ciekawe pytanie. Po odrobinie poszukiwań, "częściowe zastosowanie funkcji nie jest currying" dał najlepsze wyjaśnienie, jakie znalazłem. Nie mogę powiedzieć, że praktyczna różnica jest dla mnie szczególnie oczywista, ale nie jestem ekspertem od FP...

Kolejną przydatną stroną (którą przyznaję, że jeszcze do końca nie przeczytałem) jest "Currying i Partial Application with Java Closures" .

Wygląda na to, że jest to powszechnie Mylona para terminów, pamiętaj o tym.

 27
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-20 11:02:06

Odpowiedziałem na to w innym wątku https://stackoverflow.com/a/12846865/1685865 . W skrócie, aplikacja funkcji częściowej polega na ustaleniu pewnych argumentów danej funkcji wielowariantowej, aby uzyskać inną funkcję o mniejszej liczbie argumentów, podczas gdy Currying polega na przekształceniu funkcji N argumentów w funkcję jednoargumentową, która zwraca funkcję jednoargumentową...[Przykład Currying jest pokazany na końcu tego postu.]

Currying jest głównie teoretyczne zainteresowanie: można wyrażaj obliczenia używając tylko funkcji jednoczłonowych (tzn. każda funkcja jest jednoczłonowa). W praktyce i jako produkt uboczny, jest to technika, która może sprawić, że wiele użytecznych (ale nie wszystkie) częściowych zastosowań funkcyjnych stanie się trywialne, jeśli język ma funkcje current. Ponownie, nie jest to jedyny sposób na wdrożenie częściowych aplikacji. Można więc napotkać scenariusze, w których częściowa aplikacja jest wykonywana w inny sposób, ale ludzie mylą ją z Currying.

(przykład Curryingu)

W praktyce nie można by po prostu napisać

lambda x: lambda y: lambda z: x + y + z

Lub równoważny javascript

function (x) { return function (y){ return function (z){ return x + y + z }}}

Zamiast

lambda x, y, z: x + y + z
/ Align = "center" bgcolor = "# e0ffe0 " / cesarz chin / / align = center /
 12
Author: Ji Han,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:26

Różnica między curry a częściowym zastosowaniem może być najlepiej zilustrowana w poniższym przykładzie JavaScript:

function f(x, y, z) {
    return x + y + z;
}

var partial = f.bind(null, 1);

6 === partial(2, 3);

Częściowe zastosowanie daje funkcję o mniejszej arytmetyce; w powyższym przykładzie f ma arytmetykę 3, podczas gdy partial ma arytmetykę tylko 2. Co ważniejsze, częściowo zastosowana funkcja zwróci wynik natychmiast po wywołaniu, a nie inna funkcja w łańcuchu currying. Więc jeśli widzisz coś takiego jak partial(2)(3), to nie częściowe zastosowanie w rzeczywistości.

Czytaj dalej:

 5
Author: Kay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-27 03:51:08

Dla mnie częściowa aplikacja musi utworzyć nową funkcję, w której użyte argumenty są całkowicie zintegrowane z wynikową funkcją.

Większość języków funkcyjnych implementuje currying zwracając closure: do not evaluate under lambda when partically applied. Tak więc, aby częściowe zastosowanie było interesujące, musimy dokonać różnicy między curryingiem a częściowym zastosowaniem i rozważyć częściowe zastosowanie jako currying Plus ewaluacja w ramach lambda.

 2
Author: Taoufik Dachraoui,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-13 10:03:40

Mogę się tu bardzo mylić, ponieważ nie mam mocnego doświadczenia w matematyce teoretycznej lub programowaniu funkcyjnym, ale z mojego krótkiego zajścia w FP, wydaje się, że currying ma tendencję do przekształcania funkcji N argumentów w N funkcji jednego argumentu, podczas gdy częściowe zastosowanie [w praktyce] działa lepiej z funkcjami zmiennymi o nieokreślonej liczbie argumentów. Wiem, że niektóre przykłady w poprzednich odpowiedziach przeczą temu wyjaśnieniu, ale najbardziej pomogło mi to oddzielić koncepcje. Rozważmy ten przykład (napisany w CoffeeScript dla zwięzłości, przepraszam, jeśli to dalej myli, ale proszę poprosić o wyjaśnienie, jeśli to konieczne): {]}

# partial application
partial_apply = (func) ->
  args = [].slice.call arguments, 1
  -> func.apply null, args.concat [].slice.call arguments

sum_variadic = -> [].reduce.call arguments, (acc, num) -> acc + num

add_to_7_and_5 = partial_apply sum_variadic, 7, 5

add_to_7_and_5 10 # returns 22
add_to_7_and_5 10, 11, 12 # returns 45

# currying
curry = (func) ->
  num_args = func.length
  helper = (prev) ->
    ->
      args = prev.concat [].slice.call arguments
      return if args.length < num_args then helper args else func.apply null, args
  helper []

sum_of_three = (x, y, z) -> x + y + z
curried_sum_of_three = curry sum_of_three
curried_sum_of_three 4 # returns a function expecting more arguments
curried_sum_of_three(4)(5) # still returns a function expecting more arguments
curried_sum_of_three(4)(5)(6) # returns 15
curried_sum_of_three 4, 5, 6 # returns 15

Jest to oczywiście wymyślony przykład, ale zauważ, że częściowe zastosowanie funkcji, która akceptuje dowolną liczbę argumentów, pozwala nam wykonać funkcję, ale z pewnymi wstępnymi danymi. Funkcja jest podobna, ale pozwala nam na wykonanie funkcji n-parametru w kawałkach do, ale tylko do, wszystkie N parametry są / align = "left" /

[1]}Ponownie, to jest moje spojrzenie z rzeczy, które czytałem. Jeśli ktoś się nie zgadza, byłbym wdzięczny za komentarz, dlaczego, a nie natychmiastową negatywną opinię. Ponadto, jeśli CoffeeScript jest trudny do odczytania, proszę odwiedzić coffeescript.org kliknij "try coffeescript" i wklej mój kod, aby zobaczyć skompilowaną wersję, która może (miejmy nadzieję) nabrać większego sensu. Dzięki!
 2
Author: sunny-mittal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-07 05:07:07

Często zadawałem to pytanie podczas nauki i od tego czasu zadawano je wiele razy. Najprostszym sposobem na opisanie różnicy jest to, że oba są takie same :) pozwól mi wyjaśnić...są oczywiście różnice.

Zarówno częściowe zastosowanie, jak i currying wymagają dostarczenia argumentów do funkcji, być może nie wszystkich naraz. Dość kanonicznym przykładem jest dodawanie dwóch liczb. W pseudokodzie (właściwie JS bez słów kluczowych) funkcja bazowa może być następująca:

add = (x, y) => x + y

If I wanted funkcja "addOne", mógłbym ją częściowo zastosować lub dodać:

addOneC = curry(add, 1)
addOneP = partial(add, 1)

Teraz korzystanie z nich jest jasne:

addOneC(2) #=> 3
addOneP(2) #=> 3
Więc co za różnica? Cóż, jest to subtelne, ale częściowe zastosowanie wymaga podania pewnych argumentów i zwracana funkcja wykona główną funkcję przy następnym wywołaniu , podczas gdy currying będzie czekać aż będzie miała wszystkie niezbędne argumenty:
curriedAdd = curry(add) # notice, no args are provided
addOne = curriedAdd(1) # returns a function that can be used to provide the last argument
addOne(2) #=> returns 3, as we want

partialAdd = partial(add) # no args provided, but this still returns a function
addOne = partialAdd(1) # oops! can only use a partially applied function once, so now we're trying to add one to an undefined value (no second argument), and we get an error

W skrócie, użyj częściowej aplikacji, aby wstępnie wypełnić niektóre wartości, wiedząc, że następny czas wywołania metody, będzie ona wykonywana, pozostawiając undefined wszystkie nieprzewidziane argumenty; użyj currying, gdy chcesz stale zwracać częściowo zastosowaną funkcję tyle razy, ile jest to konieczne do wypełnienia podpisu funkcji. Ostatni przykład:

curriedAdd = curry(add)
curriedAdd()()()()()(1)(2) # ugly and dumb, but it works

partialAdd = partial(add)
partialAdd()()()()()(1)(2) # second invocation of those 7 calls fires it off with undefined parameters
Mam nadzieję, że to pomoże!

UPDATE: niektóre języki lub implementacje lib pozwolą Ci przekazać arity (całkowitą liczbę argumentów w końcowej ocenie) do częściowej implementacji aplikacji, która może łączyć moje dwa opisy do mylącego bałaganu...ale w tym momencie te dwie techniki są w dużej mierze wymienne.

 2
Author: sunny-mittal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-13 02:30:17

Currying jest funkcją jednego argumentu , który przyjmuje funkcję f i zwraca nową funkcję h:

curry(f) = h 

Częściowe zastosowanie jest funkcją złożoną z dwóch argumentów (lub więcej), która przyjmuje funkcję f i jeden lub więcej dodatkowych argumentów do f i zwraca nową funkcję g:

part(f, 2) = g

Zamieszanie powstaje, ponieważ w funkcji dwuargumentowej obowiązuje następująca równość:

partial(f, a) = curry(f)(a)

Obie strony dadzą ten sam argument funkcja.

Równość nie jest prawdziwa dla wyższych funkcji arytmetycznych, ponieważ w tym przypadku currying zwróci funkcję jednego argumentu, podczas gdy aplikacja częściowa zwróci funkcję wielu argumentów.

Różnica jest również w zachowaniu, podczas gdy currying przekształca całą pierwotną funkcję rekurencyjnie(raz dla każdego argumentu), częściowe zastosowanie jest tylko jednoetapowym zastąpieniem.

Źródło: Wikipedia Currying .

 2
Author: Roland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-10 11:24:26

Są tutaj inne świetne odpowiedzi, ale wierzę, że ten przykład (zgodnie z moim zrozumieniem) w Javie może być korzystny dla niektórych osób:

public static <A,B,X> Function< B, X > partiallyApply( BiFunction< A, B, X > aBiFunction, A aValue ){
    return b -> aBiFunction.apply( aValue, b );
}

public static <A,X> Supplier< X > partiallyApply( Function< A, X > aFunction, A aValue ){
    return () -> aFunction.apply( aValue );
}

public static <A,B,X> Function<  A, Function< B, X >  > curry( BiFunction< A, B, X > bif ){
    return a -> partiallyApply( bif, a );
}

Tak więc currying daje funkcję jednopunktową do tworzenia funkcji, gdzie partial-application tworzy funkcję wrappera, która twardo koduje jeden lub więcej argumentów.

Jeśli chcesz skopiować i wkleić, poniższe są bardziej hałaśliwe, ale bardziej przyjazne do pracy, ponieważ typy są bardziej pobłażliwe:

public static <A,B,X> Function< ? super B, ? extends X > partiallyApply( final BiFunction< ? super A, ? super B, X > aBiFunction, final A aValue ){
    return b -> aBiFunction.apply( aValue, b );
}

public static <A,X> Supplier< ? extends X > partiallyApply( final Function< ? super A, X > aFunction, final A aValue ){
    return () -> aFunction.apply( aValue );
}

public static <A,B,X> Function<  ? super A,  Function< ? super B, ? extends X >  > curry( final BiFunction< ? super A, ? super B, ? extends X > bif ){
    return a -> partiallyApply( bif, a );
}
 1
Author: ArtB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-30 20:56:21

Pisząc to, pomyliłem currying i uncurrying. Są to transformacje odwrotne do funkcji. To naprawdę nie ma znaczenia, co nazywasz które, o ile otrzymujesz to, co transformacja i jej odwrotność reprezentują.

Uncurrying nie jest zdefiniowany bardzo jasno (a raczej istnieją "sprzeczne" definicje, które wszystkie uchwyciły ducha idei). Zasadniczo oznacza to przekształcenie funkcji, która przyjmuje wiele argumentów w funkcję, która przyjmuje jeden argument. Na przykład,

(+) :: Int -> Int -> Int
Jak przekształcić to w funkcję, która przyjmuje pojedynczy argument? Oszukujesz, oczywiście!
plus :: (Int, Int) -> Int

Zauważ, że plus pobiera teraz jeden argument (który składa się z dwóch rzeczy). Super!

Jaki w tym sens? Cóż, jeśli masz funkcję, która pobiera dwa argumenty i masz parę argumentów, dobrze jest wiedzieć, że możesz zastosować tę funkcję do argumentów i nadal uzyskać to, czego oczekujesz. I, w rzeczywistości, hydraulika zrobić to już istnieje, dzięki czemu nie musisz robić takich rzeczy, jak jawne dopasowywanie wzorców. Wszystko co musisz zrobić to:
(uncurry (+)) (1,2)

Więc czym jest częściowe zastosowanie funkcji? Jest to inny sposób na przekształcenie funkcji w dwóch argumentach w funkcję z jednym argumentem. Działa to jednak inaczej. Ponownie, weźmy jako przykład ( + ). Jak możemy przekształcić go w funkcję, która przyjmuje pojedynczy Int jako argument? Oszukujemy!

((+) 0) :: Int -> Int

To funkcja, która dodaje zero do dowolnej Int.

((+) 1) :: Int -> Int

Dodaje 1 do dowolnego Int. Itd. W każdym z tych przypadków, ( + ) jest "częściowo stosowane".

 0
Author: nomen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-09 03:29:17

Prosta odpowiedź

Curry: pozwala na wywołanie funkcji, dzieląc ją na wiele wywołań, podając jeden argument na każde wywołanie.

Partial: pozwala na wywołanie funkcji, dzieląc ją na wiele wywołań, podając wiele argumentów na każde wywołanie.


Proste wskazówki

Oba pozwalają na wywołanie funkcji dostarczającej mniej argumentów (lub, lepiej, dostarczającej je kumulatywnie). Właściwie oba wiążą (przy każdym wywołaniu) określoną wartość z konkretnymi argumentami funkcja.

Prawdziwa różnica może być widoczna, gdy funkcja ma więcej niż 2 argumenty.


Proste e (c) (próbka)

(w Javascript)

function process(context, success_callback, error_callback, subject) {...}

Dlaczego zawsze przekazujesz argumenty, takie jak kontekst i wywołania zwrotne, jeśli będą zawsze takie same? Po prostu binduj niektóre wartości dla funkcji

processSubject = _.partial(process, my_context, my_success, my_error)

I wywołaj na subject1 i foobar Z

processSubject('subject1');
processSubject('foobar');
Wygodne, prawda?

With currying you ' d trzeba przekazać jeden argument na raz

curriedProcess = _.curry(process);
processWithBoundedContext = curriedProcess(my_context);
processWithCallbacks = processWithBoundedContext(my_success)(my_error); // note: these are two sequential calls

result1 = processWithCallbacks('subject1');
// same as: process(my_context, my_success, my_error, 'subject1');
result2 = processWithCallbacks('foobar'); 
// same as: process(my_context, my_success, my_error, 'foobar');

Disclaimer

Pominąłem wszystkie naukowe / matematyczne wyjaśnienia. Bo ja tego nie znam. Może pomogło
 0
Author: Kamafeather,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-10 09:56:15