Qual é a diferença entre currying e aplicação parcial?

Muitas vezes vejo na Internet várias queixas de que os exemplos de curry de outras pessoas não são curry, mas são, na verdade, apenas uma aplicação parcial.

Eu não encontrei uma explicação decente sobre o que é uma aplicação parcial ou como ela difere do currying. Parece haver uma confusão geral, com exemplos equivalentes sendo descritos como curry em alguns lugares e aplicação parcial em outros.

Alguém poderia me fornecer uma definição de ambos os termos e detalhes de como eles diferem?

Currying está convertendo uma única function de n argumentos em n funções com um único argumento cada. Dada a seguinte function:

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

Quando curried, torna-se:

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

Para obter a aplicação completa de f (x, y, z), você precisa fazer isso:

 f(x)(y)(z); 

Muitas linguagens funcionais permitem que você escreva fxyz . Se você só chama fxy ou f (x) (y), então você obtém uma function parcialmente aplicada – o valor de retorno é um fechamento de lambda(z){z(x(y))} com os valores passados ​​de x e y para f(x,y) .

Uma maneira de usar a aplicação parcial é definir funções como aplicações parciais de funções generalizadas, como 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 

A maneira mais fácil de ver como eles diferem é considerar um exemplo real . Vamos supor que temos uma function Add que recebe 2 números como input e retorna um número como saída, por exemplo, Add(7, 5) retorna 12 . Nesse caso:

  • A aplicação parcial da function Add com um valor 7 nos dará uma nova function como saída. Essa function em si leva 1 número como input e gera um número. Assim sendo:

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

    Então podemos fazer isso:

     f2 = Partial(Add, 7); f2(5); // returns 12; // f2(7)(5) is just a syntactic shortcut 
  • O processamento da function Add nos dará uma nova function como saída. Essa function em si leva 1 número como input e produz ainda outra nova function. Essa terceira function recebe 1 número como input e retorna um número como saída. Assim sendo:

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

    Então podemos fazer isso:

     f2 = Curry(Add); f3 = f2(7); f3(5); // returns 12 

Em outras palavras, “currying” e “partial application” são duas funções totalmente diferentes. Currying leva exatamente 1 input, enquanto a aplicação parcial leva 2 (ou mais) inputs.

Mesmo que ambos retornem uma function como saída, as funções retornadas são de formas totalmente diferentes, conforme demonstrado acima.

Nota: isso foi tirado do F # Basics um excelente artigo introdutório para desenvolvedores .NET entrarem em functional programming.

Currying significa dividir uma function com muitos argumentos em uma série de funções, cada uma com um argumento e, em última análise, produzindo o mesmo resultado que a function original. O currying é provavelmente o tema mais desafiador para desenvolvedores novatos em functional programming, principalmente porque é confundido com aplicações parciais. Você pode ver ambos no trabalho neste exemplo:

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

Logo, você deve ver um comportamento diferente das linguagens mais imperativas. A segunda instrução cria uma nova function chamada double, passando um argumento para uma function que leva dois. O resultado é uma function que aceita um argumento int e produz o mesmo resultado como se você tivesse chamado multiplicar com x igual a 2 e y igual a esse argumento. Em termos de comportamento, é o mesmo que este código:

 let double2 z = multiply 2 z 

Muitas vezes, as pessoas dizem erroneamente que multiplicar é curry para formar o dobro. Mas isso é apenas um pouco verdade. A function multiply é curry, mas isso acontece quando é definido porque as funções em F # são curried por padrão. Quando a function dupla é criada, é mais correto dizer que a function de multiplicação é aplicada parcialmente.

A function de multiplicação é realmente uma série de duas funções. A primeira function recebe um argumento int e retorna outra function, vinculando efetivamente x a um valor específico. Essa function também aceita um argumento int que você pode considerar como o valor a ser vinculado a y. Depois de chamar esta segunda function, xey estão ligados, portanto, o resultado é o produto de xey, conforme definido no corpo do double.

Para criar o dobro, a primeira function na cadeia de funções de multiplicação é avaliada para aplicar parcialmente a multiplicação. A function resultante recebe o nome duplo. Quando double é avaliado, ele usa seu argumento junto com o valor parcialmente aplicado para criar o resultado.

Pergunta interessante. Depois de um pouco de pesquisa, “Aplicação de function parcial não está currying” deu a melhor explicação que encontrei. Eu não posso dizer que a diferença prática é particularmente óbvia para mim, mas eu não sou especialista em FP …

Outra página de aparência útil (que confesso que ainda não li totalmente) é “Currying and Partial Application with Java Closures” .

Parece que este é um par de termos amplamente confundido, lembre-se.

Eu respondi isso em outro tópico https://stackoverflow.com/a/12846865/1685865 . Em suma, a aplicação parcial da function é sobre a fixação de alguns argumentos de uma determinada function multivariada para produzir outra function com menos argumentos, enquanto Currying é sobre transformar uma function de N argumentos em uma function unária que retorna uma function unária … [Um exemplo de O currying é mostrado no final deste post.]

Currying é principalmente de interesse teórico: pode-se expressar cálculos usando apenas funções unárias (ou seja, cada function é unária). Na prática e como um subproduto, é uma técnica que pode tornar triviais muitas aplicações funcionais parciais úteis (mas não todas), se a linguagem tiver funções curry. Mais uma vez, não é o único meio para implementar aplicativos parciais. Assim, você pode encontrar cenários em que a aplicação parcial é feita de outra maneira, mas as pessoas estão confundindo isso com Currying.

(Exemplo de Currying)

Na prática, não basta escrever

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

ou o equivalente javascript

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

ao invés de

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

por uma questão de currying.

A diferença entre o curry e a aplicação parcial pode ser melhor ilustrada por meio do seguinte exemplo de JavaScript:

 function f(x, y, z) { return x + y + z; } var partial = f.bind(null, 1); 6 === partial(2, 3); 

A aplicação parcial resulta em uma function de aridade menor; no exemplo acima, f tem uma aridade de 3, enquanto partial tem apenas uma aridade de 2. Mais importante, uma function parcialmente aplicada retornaria o resultado imediatamente após ser invocada , e não outra function na cadeia de currying. Então, se você está vendo algo parecido como partial(2)(3) , não é uma aplicação parcial na realidade.

Leitura adicional:

  • Programação Funcional em 5 minutos
  • Currying: Contraste com Aplicação de Função Parcial

Para mim, a aplicação parcial deve criar uma nova function em que os argumentos usados ​​sejam completamente integrados à function resultante.

A maioria das linguagens funcionais implementa curry retornando um fechamento: não avalie sob lambda quando aplicado parcialmente. Portanto, para que a aplicação parcial seja interessante, precisamos fazer uma diferença entre o currying e a aplicação parcial e considerar a aplicação parcial como currying plus evaluation sob lambda.

Eu poderia estar muito errado aqui, já que não tenho um forte conhecimento em matemática teórica ou functional programming, mas da minha breve incursão em FP, parece que currying tende a transformar uma function de N argumentos em funções N de um argumento, enquanto a aplicação parcial [na prática] funciona melhor com funções variadicas com um número indeterminado de argumentos. Eu sei que alguns dos exemplos nas respostas anteriores desafiam essa explicação, mas me ajudou mais a separar os conceitos. Considere este exemplo (escrito em CoffeeScript por sucessão, minhas desculpas se confundir ainda mais, mas por favor peça esclarecimentos, se necessário):

 # 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 

Este é obviamente um exemplo inventado, mas observe que a aplicação parcial de uma function que aceita qualquer número de argumentos nos permite executar uma function, mas com alguns dados preliminares. O processamento de uma function é semelhante, mas nos permite executar uma function de parâmetro N em partes até que, mas somente até, todos os parâmetros N sejam contabilizados.

Mais uma vez, esta é a minha opinião sobre as coisas que li. Se alguém discordar, eu apreciaria um comentário sobre o porquê, em vez de um voto negativo imediato. Além disso, se o CoffeeScript é difícil de ler, visite coffeescript.org, clique em “experimentar o coffeescript” e cole no meu código para ver a versão compilada, que pode (espero) fazer mais sentido. Obrigado!

Eu tive essa pergunta muito enquanto aprendia e desde então tenho sido perguntado muitas vezes. A maneira mais simples que posso descrever a diferença é que ambos são os mesmos 🙂 Deixe-me explicar … obviamente existem diferenças.

Tanto a aplicação parcial quanto o curry envolvem o fornecimento de argumentos para uma function, talvez não de uma só vez. Um exemplo bastante canônico é adicionar dois números. No pseudocódigo (na verdade, JS sem palavras-chave), a function base pode ser a seguinte:

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

Se eu quisesse uma function “addOne”, eu poderia aplicá-la parcialmente ou curry:

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

Agora, usá-los é claro:

 addOneC(2) #=> 3 addOneP(2) #=> 3 

Então qual a diferença? Bem, é sutil, mas a aplicação parcial envolve o fornecimento de alguns argumentos e a function retornada executará a function principal na próxima chamada, enquanto o currying continuará esperando até que ela tenha todos os argumentos necessários:

 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 

Em suma, use a aplicação parcial para pré-preencher alguns valores, sabendo que na próxima vez que você chamar o método, ele será executado, deixando indefinidos todos os argumentos não-fornecidos; use currying quando quiser retornar continuamente uma function parcialmente aplicada quantas vezes forem necessárias para preencher a assinatura da function. Um exemplo final inventado:

 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 

Espero que isto ajude!

ATUALIZAÇÃO: Algumas linguagens ou implementações lib permitirão que você passe uma aridade (número total de argumentos na avaliação final) para a implementação parcial da aplicação, o que pode confundir minhas duas descrições em uma confusão confusa … mas, nesse ponto, as duas técnicas são amplamente intercambiável.

O currying é uma function de um argumento que recebe uma function f e retorna uma nova function h :

 curry(f) = h 

A aplicação parcial é uma function de dois (ou mais) argumentos que toma uma function f e um ou mais argumentos adicionais para f e retorna uma nova function g :

 part(f, 2) = g 

A confusão surge porque, com uma function de dois argumentos, a seguinte igualdade se mantém:

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

Ambos os lados produzirão a mesma function de um argumento.

A igualdade não é verdadeira para funções de aridade superior porque neste caso o currying retornará uma function de um argumento, enquanto a aplicação parcial retornará uma function de argumento múltiplo.

A diferença também está no comportamento, enquanto o currying transforma a function original inteira recursivamente (uma vez para cada argumento), a aplicação parcial é apenas uma substituição de um passo.

Fonte: Wikipedia Currying .

Ao escrever isso, confundi curinging e uncurrying. São transformações inversas nas funções. Realmente não importa o que você chama de qual, contanto que você consiga o que a transformação e seu inverso representam.

A falta de pressa não é definida com muita clareza (ou melhor, há definições “conflitantes” que captam o espírito da ideia). Basicamente, isso significa transformar uma function que leva vários argumentos em uma function que usa um único argumento. Por exemplo,

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

Agora, como você transforma isso em uma function que leva um único argumento? Você engana, é claro!

 plus :: (Int, Int) -> Int 

Observe que plus agora recebe um único argumento (composto de duas coisas). Super!

Qual é o objective disso? Bem, se você tem uma function que aceita dois argumentos, e você tem um par de argumentos, é bom saber que você pode aplicar a function aos argumentos e ainda obter o que espera. E, de fato, o encanamento para fazer isso já existe, de modo que você não precisa fazer coisas como correspondência explícita de padrões. Tudo o que tem a fazer é:

 (uncurry (+)) (1,2) 

Então, qual é o aplicativo de function parcial? É uma maneira diferente de transformar uma function em dois argumentos em uma function com um argumento. Funciona de forma diferente embora. Novamente, vamos pegar (+) como exemplo. Como podemos transformá-lo em uma function que usa um único Int como argumento? Nós enganamos!

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

Essa é a function que adiciona zero a qualquer Int.

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

adiciona 1 a qualquer Int. Etc. Em cada um desses casos, (+) é “parcialmente aplicado”.

Resposta simples

Caril: permite chamar uma function, dividindo-a em várias chamadas, fornecendo um argumento por chamada.

Parcial: permite chamar uma function, dividindo-a em várias chamadas, fornecendo vários argumentos por chamada.


Dicas simples

Ambos permitem que você chame uma function fornecendo menos argumentos (ou, melhor, fornecendo-os cumulativamente). Na verdade, ambos ligam (em cada chamada) um valor específico a argumentos específicos da function.

A diferença real pode ser vista quando a function tem mais de 2 argumentos.


Simples e (c) (amostra)

(em Javascript)

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

Por que passar sempre os argumentos, como o contexto e os retornos de chamada, se eles serão sempre os mesmos? Apenas ligue alguns valores para a function

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

e chame no subject1 e foobar com

 processSubject('subject1'); processSubject('foobar'); 

Confortável, não é? 😉

Com curry você precisaria passar um argumento por vez

 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'); 

aviso Legal

Eu pulei toda a explicação acadêmica / matemática. Porque eu não sei disso. Talvez tenha ajudado 🙃