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


438

Muitas vezes vejo na Internet várias queixas de que os exemplos de currying de outras pessoas não são currying, mas na verdade são apenas aplicações parciais.

Não encontrei uma explicação decente sobre o que é uma aplicação parcial ou como ela difere do curry. Parece haver uma confusão geral, com exemplos equivalentes sendo descritos como currying 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?

Respostas:


256

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

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

Quando curry, 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 o seguinte:

f(x)(y)(z);

Muitas linguagens funcionais permitem que você escreva f x y z. Se você chamar apenas f x you f (x) (y) , obterá uma função parcialmente aplicada - o valor de retorno é um fechamento lambda(z){z(x(y))}com valores passados ​​de x e y para f(x,y).

Uma maneira de usar aplicativos parciais é definir funções como aplicativos parciais de funções generalizadas, como fold :

function fold(combineFunction, accumulator, 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(accumulator,list) {/*...*/}
f(0,list) //returns 10
@g = f(0) //same as sum
g(list)  //returns 10

40
Você está dizendo que a aplicação parcial é quando você seleciona uma função e usa algumas, mas não todas as funções resultantes?
SpoonMeiser 20/10/08

9
mais ou menos sim. Se você só fornecer um subconjunto dos argumentos, você vai receber de volta uma função que aceita o resto dos argumentos
Mark Cidade

1
Alterar uma função f (a, b, c, d) para g (a, b) conta como aplicação parcial? Ou é apenas quando aplicado a funções com caril? Desculpe ser uma dor, mas estou buscando uma resposta explícita aqui.
SpoonMeiser 20/10/08

2
@ Mark: Eu acho que esse é apenas um daqueles conceitos que traz à tona o pedante em mim - mas um apelo a fontes autorizadas faz pouco para satisfazer, pois todos parecem apontar um para o outro. A Wikipedia dificilmente é o que considero uma fonte autorizada, mas entendo que é difícil encontrar muito mais. Basta dizer que acho que nós dois sabemos o que falamos e o poder dele, independentemente de podermos concordar ou não (ou discordar) sobre os detalhes do vernáculo! :) Obrigado Mark!
Jason Bunting

5
@ JasonBunting, em relação ao seu primeiro comentário, o que você estava falando é decaimento . O currying usa uma função de vários argumentos como entrada e retorna uma cadeia de funções de 1 argumentos como saída. A remoção do currying leva uma cadeia de funções 1-arg como entrada e retorna uma função multi-arg como saída. Conforme elaborado em stackoverflow.com/a/23438430/632951
Pacerier

165

A maneira mais fácil de ver como elas diferem é considerar um exemplo real . Vamos assumir que temos uma função Addque recebe 2 números como entrada e retorna um número como saída, por exemplo, Add(7, 5)retorna 12. Nesse caso:

  • A aplicação parcial da função Addcom um valor 7nos dará uma nova função como saída. Essa função em si recebe 1 número como entrada 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
    

    Para que possamos fazer isso:

    f2 = Partial(Add, 7);
    f2(5); // returns 12;
           // f2(7)(5) is just a syntactic shortcut
    
  • Currying a função Addnos dará uma nova função como saída. Essa função em si recebe 1 número como entrada e gera mais uma nova função. Essa terceira função recebe 1 número como entrada 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
                // i.e. f2(number) = f3
    
                // f3 takes 1 number as input and returns a number as output
                // i.e. f3(number) = number
    

    Para que possamos fazer isso:

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

Em outras palavras, "currying" e "aplicação parcial" são duas funções totalmente diferentes. O curry requer exatamente 1 entrada, enquanto a aplicação parcial requer 2 (ou mais) entradas.

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


24
O aplicativo parcial transforma uma função de n-arypara (x - n)-ary, curry de n-arypara n * 1-ary. Uma função parcialmente aplicada possui um escopo reduzido (de aplicativo), ou seja, Add7é menos expressivo que Add. Uma função ao curry, por outro lado, é tão expressiva quanto a função original.
bob

4
Acredito que a característica mais distintiva é quando curry f (x, y, z) => R, obtemos f (x) que retorna g (y) => h (z) => R, cada um consumindo um único argumento; mas quando aplicamos parcialmente f (x, y, z) como f (x) obtemos g (y, z) => R, ou seja, com dois argumentos. Se não fosse por essa característica, poderíamos dizer que currying é como aplicação parcial a 0 argumentos, deixando todos os argumentos sem limites; no entanto, na realidade, f () parcialmente aplicado a 0 argumentos é uma função que consome 3 args de uma só vez, diferentemente de f () com caril.
Maksim Gumerov

2
Mais uma vez, a resposta correta não é a primeira ou a mais votada: a simples explicação da assinatura de curry versus parcial no final desta resposta é realmente a maneira mais fácil de resolver a questão.
FNL

2
O que o comentário f2(7)(5) is just a syntactic shortcutsignifica? (Eu sei muito pouco.) Ainda não f2contém / "conhece" 7?
Zach Mierzejewski

@Pacerier, existe uma curryimplementação em algum lugar (não pense que está functools)
alancalvitti

51

Nota: foi extraído do F # Basics um excelente artigo introdutório para desenvolvedores .NET que ingressaram na programação funcional.

Currying significa dividir uma função com muitos argumentos em uma série de funções, cada uma recebendo um argumento e, finalmente, produzindo o mesmo resultado que a função original. O curry é provavelmente o tópico mais desafiador para desenvolvedores iniciantes em programação funcional, principalmente porque geralmente é confundido com aplicativo parcial. Você pode ver os dois trabalhando neste exemplo:

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

Imediatamente, você deve ver um comportamento diferente da maioria das linguagens imperativas. A segunda instrução cria uma nova função chamada double, passando um argumento para uma função que leva dois. O resultado é uma função que aceita um argumento int e produz a mesma saída 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 verdadeiro. A função multiplicar é alterada, mas isso acontece quando é definida porque as funções em F # são alteradas por padrão. Quando a função dupla é criada, é mais preciso dizer que a função multiplicar é parcialmente aplicada.

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

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


33

Pergunta interessante. Após algumas pesquisas, "O aplicativo de função parcial não está se recuperando" deu a melhor explicação que encontrei. Não posso dizer que a diferença prática seja particularmente óbvia para mim, mas não sou especialista em FP ...

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

Parece que este é um par de termos amplamente confuso, veja bem.


5
O primeiro link é direto sobre as diferenças. Aqui está outra que eu encontrei útil: bit.ly/CurryingVersusPartialApplication
Jason Bunting

5
Currying tem a ver com tuplas (transformar uma função que leva um argumento de tupla em uma que aceita n argumentos separados e vice-versa). Aplicação parcial é a capacidade de aplicar uma função a alguns argumentos, produzindo uma nova função para os argumentos restantes. É fácil lembrar se você pensa em curry == relacionado a tuplas.
11118 Don Stewart

9
Os links @ Jon publicados são informativos, mas será melhor expandir sua resposta e adicionar mais algumas informações aqui.
Zaheer Ahmed


11
Não posso acreditar que você recebeu 20 votos positivos para alguns links e uma admissão que realmente não sabe a diferença entre curry e aplicação parcial. Bem jogado, senhor.
AlienWebguy

16

Eu respondi isso em outro tópico https://stackoverflow.com/a/12846865/1685865 . Em resumo, a aplicação parcial da função consiste em corrigir alguns argumentos de uma determinada função multivariável para gerar outra função com menos argumentos, enquanto o Currying trata de transformar uma função dos argumentos N em uma função unária que retorna uma função unária ... [Um exemplo de O curry é mostrado no final deste post.]

O curry é principalmente de interesse teórico: é possível expressar cálculos usando apenas funções unárias (ou seja, todas as funções são unárias). Na prática e como subproduto, é uma técnica que pode tornar triviais muitos aplicativos funcionais parciais úteis (mas não todos), se o idioma tiver funções curry. Novamente, não é o único meio de 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-a com curry.

(Exemplo de caril)

Na prática, não se escreveria apenas

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

ou o javascript equivalente

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.


1
Você diria que o curry é um caso específico de aplicação parcial?
SpoonMeiser

1
@SpoonMeiser, Não, currying não é um caso específico de aplicação parcial: uma aplicação parcial de uma função de 2 entradas não é a mesma coisa que currying da função. Consulte stackoverflow.com/a/23438430/632951 .
Pacerier

10

Currying é uma função de um argumento que pega uma função fe retorna uma nova função h. Observe que hrecebe um argumento Xe retorna uma função que mapeia Ypara Z:

curry(f) = h 
f: (X x Y) -> Z 
h: X -> (Y -> Z)

Aplicação parcial é uma função de dois (ou mais) argumentos que levam uma função fe um ou mais argumentos adicionais para fe retornam uma nova função g:

part(f, 2) = g
f: (X x Y) -> Z 
g: Y -> Z

A confusão surge porque, com uma função de dois argumentos, a seguinte igualdade é válida:

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

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

A igualdade não é verdadeira para funções de aridade superior, porque nesse caso o curry retornará uma função de um argumento, enquanto o aplicativo parcial retornará uma função de vários argumentos.

A diferença também está no comportamento, enquanto o curry transforma toda a função original recursivamente (uma vez para cada argumento), a aplicação parcial é apenas uma substituição de uma etapa.

Fonte: Wikipedia Currying .


8

A diferença entre aplicação curry e aplicação parcial pode ser melhor ilustrada através deste exemplo de JavaScript a seguir:

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 função de menor aridade; no exemplo acima, fpossui uma área de 3, enquanto partialapenas 2. Mais importante, uma função parcialmente aplicada retornaria o resultado imediatamente ao ser chamada , e não outra função na cadeia de curry. Portanto, se você estiver vendo algo parecido partial(2)(3), não é uma aplicação parcial na realidade.

Leitura adicional:


"uma função parcialmente aplicada retornaria o resultado imediatamente ao ser chamada" - isso não está correto, está? quando aplico parcialmente uma função, essa expressão retorna uma função, não "um resultado". Ok, você provavelmente quis dizer que esta última função, quando chamada com os argumentos restantes, retorna o resultado, ao contrário de cavar um passo para o curry. Mas ninguém realmente diz que você precisa especificar todos os argumentos restantes: você pode aplicar parcialmente o resultado da aplicação parcial, e isso mais uma vez será uma função, não um "resultado"
Maksim Gumerov 08/17

6

Resposta simples

Curry: permite chamar uma função, dividindo-a em várias chamadas, fornecendo um argumento por chamada.

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


Dicas simples

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

A diferença real pode ser vista quando a função possui mais de 2 argumentos.


Simples e (c) (amostra)

(em Javascript)

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

por que sempre passar os argumentos, como o contexto e os retornos de chamada, se eles serão sempre os mesmos? Basta ligar alguns valores para a função

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

e chame-o de subject1 e foobar com

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

Confortável, não é? 😉

Com o curry, você precisa 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. Talvez tenha ajudado


4

Eu tive essa pergunta muito enquanto aprendia e, desde então, já a fiz várias vezes. A maneira mais simples de descrever a diferença é que ambas são iguais :) Deixe-me explicar ... obviamente existem diferenças.

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

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

Se eu quisesse uma função "addOne", 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 função retornada executará a função principal na próxima chamada, enquanto o currying continuará esperando até que 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 resumo, use o aplicativo parcial para 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 desejar retornar continuamente uma função parcialmente aplicada quantas vezes for necessário para cumprir a assinatura da função. Um exemplo final artificial:

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 implementações de linguagens ou libs permitem que você passe uma aridade (número total de argumentos na avaliação final) para a implementação parcial do aplicativo, que pode confundir minhas duas descrições em uma confusão confusa ... mas nesse momento, as duas técnicas são amplamente intercambiável.


3

Para mim, a aplicação parcial deve criar uma nova função na qual os argumentos usados ​​sejam completamente integrados à função resultante.

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


3

Eu poderia estar muito errado aqui, já que não tenho uma sólida formação em matemática teórica ou programação funcional, mas da minha breve incursão no FP, parece que o curry tende a transformar uma função de N argumentos em N funções de um argumento, enquanto a aplicação parcial [na prática] funciona melhor com funções variadas com um número indeterminado de argumentos. Sei que alguns dos exemplos das respostas anteriores desafiam essa explicação, mas isso me ajudou a separar os conceitos. Considere este exemplo (escrito em CoffeeScript para ser sucinto, minhas desculpas se isso for mais confuso, mas 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 artificial, mas observe que a aplicação parcial de uma função que aceita qualquer número de argumentos nos permite executar uma função, mas com alguns dados preliminares. O curry de uma função é semelhante, mas nos permite executar uma função de parâmetro N em partes até, mas somente até, todos os parâmetros N serem contabilizados.

Novamente, 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 for difícil de ler, visite coffeescript.org, clique em "try coffeescript" e cole no meu código para ver a versão compilada, o que (espero) pode fazer mais sentido. Obrigado!


2

Vou assumir que a maioria das pessoas que fazem essa pergunta já está familiarizada com os conceitos básicos, portanto não há necessidade de falar sobre isso. É a sobreposição que é a parte confusa.

Você pode usar completamente os conceitos, mas os entende juntos como esse borrão conceitual amorfo pseudo-atômico. O que está faltando é saber onde está a fronteira entre eles.

Em vez de definir o que cada um é, é mais fácil destacar apenas suas diferenças - o limite.

Currying é quando você define a função.

Aplicação parcial é quando você chama a função.

Aplicação é matemática-falar para chamar uma função.

A aplicação parcial requer a chamada de uma função com curry e a obtenção de uma função como o tipo de retorno.


1

Existem outras ótimas respostas aqui, mas acredito que este exemplo (conforme meu entendimento) em Java pode ser benéfico para algumas pessoas:

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

Portanto, o currying fornece uma função de um argumento para criar funções, em que o aplicativo parcial cria uma função de invólucro que codifica um ou mais argumentos.

Se você deseja copiar e colar, o seguinte é mais barulhento, mas mais fácil de trabalhar, já que os tipos são mais brandos:

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

O seguinte me deu a principal visão: "Portanto, o currying fornece uma função de um argumento para criar funções, onde o aplicativo parcial cria uma função de invólucro que codifica um ou mais argumentos".
Roland

0

Ao escrever isso, confundi curry e uncurrying. São transformações inversas em funções. Realmente não importa o que você chama de qual, desde que você consiga o que a transformação e sua inversa representam.

Incansavelmente não é definido com muita clareza (ou melhor, existem definições "conflitantes" que capturam o espírito da idéia). Basicamente, significa transformar uma função que usa vários argumentos em uma função que usa um único argumento. Por exemplo,

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

Agora, como você transforma isso em uma função que requer um único argumento? Você trapaceia, é claro!

plus :: (Int, Int) -> Int

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

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

(uncurry (+)) (1,2)

Então, o que é aplicação parcial de função? É uma maneira diferente de transformar uma função em dois argumentos em uma função com um argumento. Porém, funciona de maneira diferente. Mais uma vez, tomemos (+) como exemplo. Como podemos transformá-lo em uma função que usa um único Int como argumento? Nós trapaceamos!

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

Essa é a função que adiciona zero a qualquer Int.

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

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.