O que há de especial no curry ou na aplicação parcial?


9

Eu tenho lido artigos sobre programação funcional todos os dias e tentado aplicar algumas práticas o máximo possível. Mas não entendo o que é único no curry ou na aplicação parcial.

Tome este código Groovy como um exemplo:

def mul = { a, b -> a * b }
def tripler1 = mul.curry(3)
def tripler2 = { mul(3, it) }

Eu não entendo qual é a diferença entre tripler1e tripler2. Eles não são os mesmos? O 'currying' é suportado em linguagens funcionais puras ou parciais, como Groovy, Scala, Haskell, etc. função ou fechamento que encaminhará os parâmetros para a função original (como tripler2) na maioria dos idiomas (até C.)

Estou faltando alguma coisa aqui? Há lugares em que posso usar curry e aplicação parcial no meu aplicativo Grails, mas hesito em fazê-lo porque estou me perguntando "Qual é a diferença?"

Por favor me esclareça.

EDIT: Vocês estão dizendo que aplicação parcial / currying é simplesmente mais eficiente do que criar / chamar outra função que encaminha parâmetros padrão para a função original?


11
Alguém pode criar as tags "curry" ou "currying"?
Vigneshwaran

Como você se curry em C?
Giorgio

isso provavelmente é realmente mais sobre programadores de
jk.

11
@Vigneshwaran: AFAIK, você não precisa criar outra função em um idioma que suporte o curry. Por exemplo, em Haskell f x y = x + ysignifica que fé uma função que aceita um parâmetro int. O resultado de f x( faplicado a x) é uma função que aceita um parâmetro int. O resultado f x y(ou (f x) yseja, f xaplicado a y) é uma expressão que não aceita parâmetros de entrada e é avaliada pela redução x + y.
Giorgio

11
Você pode conseguir as mesmas coisas, mas a quantidade de esforço que você passar com C é muito mais doloroso e não tão eficiente quanto em uma linguagem como Haskell, onde é o comportamento padrão
Daniel Gratzer

Respostas:


8

Currying consiste em transformar / representar uma função que recebe n entradas em n funções em que cada uma recebe 1 entrada. Aplicação parcial é sobre como fixar algumas das entradas em uma função.

A motivação para a aplicação parcial é principalmente o fato de facilitar a gravação de bibliotecas de funções de ordem superior. Por exemplo, todos os algoritmos do C ++ STL usam predicados ou funções unárias, o bind1st permite que o usuário da biblioteca conecte funções não unárias com um valor vinculado. O escritor da biblioteca, portanto, não precisa fornecer funções sobrecarregadas para todos os algoritmos que executam funções unárias para fornecer versões binárias

O curry em si é útil porque oferece aplicação parcial em qualquer lugar que você desejar de graça, ou seja, você não precisa mais de uma função bind1stpara aplicar parcialmente.


é curryingalgo específico para o groovy ou aplicável em vários idiomas?
amphibient

@foampile é algo que é aplicável em todos os idiomas, mas o curry ironicamente groovy realmente não faz isso programmers.stackexchange.com/questions/152868/…
jk.

@jk. Você está dizendo que a aplicação parcial / curry é mais eficiente do que criar e chamar outra função?
Vigneshwaran

2
@Vigneshwaran - não é necessariamente mais eficiente, mas é definitivamente mais eficiente em termos de tempo do programador. Observe também que, enquanto o currying é suportado por muitas linguagens funcionais, mas geralmente não é suportado nas linguagens OO ou processuais. (Ou pelo menos, não pela própria linguagem.)
Stephen C

6

Mas eu posso fazer a mesma coisa (aplicação com curry esquerdo, curry direito, n-curry ou parcial) simplesmente criando outra função ou fechamento nomeado ou anônimo que encaminhará os parâmetros para a função original (como tripler2) na maioria dos idiomas ( mesmo C.)

E o otimizador analisará isso e seguirá rapidamente para algo que possa entender. O curry é um pequeno truque para o usuário final, mas possui benefícios muito melhores do ponto de vista do design de linguagem. É muito bom lidar com todos os métodos como unários, A -> Bonde Bpode haver outro método.

Ele simplifica os métodos que você precisa escrever para lidar com funções de ordem superior. Sua análise estática e otimização no idioma têm apenas um caminho para trabalhar com esse comportamento de maneira conhecida. A ligação de parâmetros simplesmente cai fora do design, em vez de exigir a utilização de argolas para executar esse comportamento comum.


6

Como @jk. Como mencionado, o currying pode ajudar a tornar o código mais geral.

Por exemplo, suponha que você tenha essas três funções (em Haskell):

> let q a b = (2 + a) * b

> let r g = g 3

> let f a b = b (a 1)

A função faqui assume duas funções como argumentos, passa 1para a primeira função e passa o resultado da primeira chamada para a segunda função.

Se chamássemos fusing qe rcomo argumentos, estaria efetivamente:

> r (q 1)

onde qseria aplicado 1e retornaria outra função (como qé exibida); essa função retornada seria então passada para rcomo argumento a ser dado um argumento de 3. O resultado disso seria um valor de 9.

Agora, digamos que tivemos duas outras funções:

> let s a = 3 * a

> let t a = 4 + a

também podemos passar esses valores fe obter um valor de 7or 15, dependendo de nossos argumentos serem s tor t s. Como essas funções retornam um valor em vez de uma função, nenhuma aplicação parcial ocorreria em f s tou f t s.

Se tivéssemos escrito fcom qe rem mente, poderíamos ter usado um lambda (função anônima) em vez de aplicação parcial, por exemplo:

> let f' a b = b (\x -> a 1 x)

mas isso teria restringido a generalidade de f'. fpode ser chamado com argumentos qe rou se t, mas f'só pode ser chamado com qe r- f' s te f' t sambos resultam em um erro.

MAIS

Se f'fosse chamado com um par q'/ r'em q'que o argumento tivesse mais de dois argumentos, q'ele ainda acabaria sendo parcialmente aplicado f'.

Como alternativa, você pode quebrar qfora ou fnão dentro, mas isso deixaria uma lambda aninhada desagradável:

f (\x -> (\y -> q x y)) r

que é essencialmente o que o caril qestava em primeiro lugar!


Você abriu meus olhos. Sua resposta me fez perceber como as funções com curry / parcialmente aplicadas são diferentes da criação de uma nova função que passa argumentos para a função original. 1. Passando em torno curryied funções / Paed (como f (q.curry (2)) são mais puro do que a criação de funções separadas desnecessariamente para apenas uma utilização temporária (em linguagens funcionais como Groovy).
Vigneshwaran

2. Na minha pergunta, eu disse: "Eu posso fazer o mesmo em C." Sim, mas em idiomas não funcionais, nos quais você não pode passar funções como dados, criar uma função separada, que encaminha parâmetros para o original, não possui todos os benefícios de currying / pa
Vigneshwaran

Percebi que o Groovy não suporta o tipo de generalização que Haskell suporta. Eu tive que escrever def f = { a, b -> b a.curry(1) }para fazer f q, rpara trabalhar e def f = { a, b -> b a(1) }ou def f = { a, b -> b a.curry(1)() }para f s, ttrabalhar. Você precisa passar todos os parâmetros ou dizer explicitamente que está currying. :(
Vigneshwaran

2
@Vigneshwaran: Sim, é seguro dizer que Haskell e currying combinam muito bem . ;] Observe que, em Haskell, as funções são curry (na definição correta) por padrão e o espaço em branco indica a aplicação da função, portanto f x y, o que muitos idiomas escreveriam f(x)(y), não f(x, y). Talvez seu código funcione no Groovy se você escrever qpara que ele seja chamado como q(1)(2)?
CA McCann

11
@Vigneshwaran Fico feliz em poder ajudar! Sinto sua dor por ter que dizer explicitamente que você está fazendo uma aplicação parcial. No Clojure, eu tenho que fazer (partial f a b ...)- já que estou acostumado a Haskell, sinto muita falta de curry adequado ao programar em outras linguagens (embora eu esteja trabalhando recentemente no F #, que felizmente o suporta).
paul

3

Existem dois pontos principais sobre a aplicação parcial. A primeira é sintática / conveniente - algumas definições se tornam mais fáceis e curtas de ler e escrever, como o @jk mencionou. (Confira a programação do Pointfree para saber mais sobre como isso é incrível!)

O segundo, como @telastyn mencionou, é sobre um modelo de funções e não é apenas conveniente. Na versão Haskell, da qual vou obter meus exemplos porque não estou familiarizado com outras linguagens com aplicação parcial, todas as funções usam um único argumento. Sim, até funções como:

(:) :: a -> [a] -> [a]

pegue um único argumento; devido à associatividade do construtor do tipo de função ->, o acima é equivalente a:

(:) :: a -> ([a] -> [a])

que é uma função que recebe ae retorna uma função [a] -> [a].

Isso nos permite escrever funções como:

($) :: (a -> b) -> a -> b

que pode aplicar qualquer função a um argumento do tipo apropriado. Mesmo os loucos, como:

f :: (t, t1) -> t -> t1 -> (t2 -> t3 -> (t, t1)) -> t2 -> t3 -> [(t, t1)]
f q r s t u v = q : (r, s) : [t u v]

f' :: () -> Char -> (t2 -> t3 -> ((), Char)) -> t2 -> t3 -> [((), Char)]
f' = f $ ((), 'a')  -- <== works fine

Ok, então esse foi um exemplo artificial. Mas uma mais útil envolve a classe do tipo Aplicative , que inclui este método:

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

Como você pode ver, o tipo é idêntico $se você remover o Applicative fbit e, de fato, essa classe descreve a aplicação de funções em um contexto. Então, em vez da aplicação da função normal:

ghci> map (+3) [1..5]  
[4,5,6,7,8]

Podemos aplicar funções em um contexto Aplicativo; por exemplo, no contexto Talvez, em que algo pode estar presente ou ausente:

ghci> Just map <*> Just (+3) <*> Just [1..5]
Just [4,5,6,7,8]

ghci> Just map <*> Nothing <*> Just [1..5]
Nothing

Agora, a parte mais interessante é que a classe do tipo Applicative não menciona nada sobre funções de mais de um argumento - no entanto, pode lidar com elas, inclusive funções de 6 argumentos como f:

fA' :: Maybe (() -> Char -> (t2 -> t3 -> ((), Char)) -> t2 -> t3 -> [((), Char)])
fA' = Just f <*> Just ((), 'a')

Até onde eu sei, a classe do tipo Aplicativo em sua forma geral não seria possível sem alguma concepção de aplicação parcial. (Para qualquer especialista em programação existente - corrija-me se eu estiver errado!) É claro que, se sua linguagem não possui aplicação parcial, você pode construí-la de alguma forma, mas ... não é a mesma coisa, é? ? :)


11
Applicativesem currying ou aplicação parcial usaria fzip :: (f a, f b) -> f (a, b). Em um idioma com funções de ordem superior, isso permite elevar a aplicação de currying e parcial no contexto do functor e é equivalente a (<*>). Sem funções de ordem superior, você não terá, fmapentão a coisa toda seria inútil.
CA McCann

@CAMcCann obrigado pelo feedback! Eu sabia que estava enlouquecendo com esta resposta. Então, o que eu disse errado?

11
É correto em espírito, certamente. Dividir os cabelos sobre as definições de "forma geral", "possível" e ter uma "concepção de aplicação parcial" não mudará o simples fato de que o f <$> x <*> yestilo idiomático encantador funciona facilmente porque o curry e a aplicação parcial funcionam facilmente. Em outras palavras, o que é agradável é mais importante do que é possível aqui.
CA McCann

Toda vez que vejo exemplos de código de programação funcional, estou mais convencido de que é uma piada elaborada e que não existe.
Kieveli

11
@Kieveli, é uma pena que você se sinta assim. Existem muitos bons tutoriais por aí que o ajudarão a começar com uma boa compreensão do básico.
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.