O groovy chama o aplicativo parcial de 'currying'?

15

Groovy tem um conceito que chama de 'currying'. Aqui está um exemplo de seu wiki:

def divide = { a, b -> a / b }

def halver = divide.rcurry(2)

assert halver(8) == 4

Meu entendimento do que está acontecendo aqui é que o argumento da direita divideestá vinculado ao valor 2. Isso parece uma forma de aplicação parcial.

O termo currying geralmente é usado para significar transformar uma função que leva uma série de argumentos em uma função que leva apenas um argumento e retorna outra função. Por exemplo, aqui está o tipo da curryfunção em Haskell:

curry :: ((a, b) -> c) -> (a -> (b -> c))

Para as pessoas que não usaram Haskell a, be csão todos os parâmetros genéricos. curryrecebe uma função com dois argumentos e retorna uma função que recebe ae retorna uma função de bpara c. Adicionei um par extra de colchetes ao tipo para deixar isso mais claro.

Eu entendi mal o que está acontecendo no exemplo groovy ou é apenas uma aplicação parcial com o nome errado? Ou, de fato, faz as duas coisas: ou seja, converte-se divideem uma função ao curry e depois se aplica parcialmente 2a essa nova função.

Richard Warburton
fonte
1
para quem não fala haskell msmvps.com/blogs/jon_skeet/archive/2012/01/30/…
jk.

Respostas:

14

A implementação de Groovy na curryverdade não se curry a qualquer momento, mesmo nos bastidores. É essencialmente idêntico à aplicação parcial.

Os métodos curry, rcurrye retornam um objeto que contém os argumentos vinculados. Ele também possui um método (denominado incorretamente - funções curry, não argumentos) que retorna a composição dos argumentos passados ​​a ele com os argumentos encadernados.ncurryCurriedClosuregetUncurriedArguments

Quando um fechamento é chamado, ele finalmente chama o invokeMethodmétodo deMetaClassImpl , que verifica explicitamente se o objeto de chamada é uma instância de CurriedClosure. Nesse caso, ele usa o mencionado acima getUncurriedArgumentspara compor a matriz completa de argumentos a serem aplicados:

if (objectClass == CurriedClosure.class) {
    // ...
    final Object[] curriedArguments = cc.getUncurriedArguments(arguments);
    // [Ed: Yes, you read that right, curried = uncurried. :) ]
    // ...
    return ownerMetaClass.invokeMethod(owner, methodName, curriedArguments);
}

Com base na nomenclatura confusa e um tanto inconsistente acima, desconfio que quem escreveu isso tenha um bom entendimento conceitual, mas talvez tenha sido um pouco apressado e - como muitas pessoas inteligentes - conflitou com a aplicação parcial. Isso é compreensível (veja a resposta de Paul King), se um pouco infeliz; será difícil corrigir isso sem quebrar a compatibilidade com versões anteriores.

Uma solução que sugeri é sobrecarregar o currymétodo, de modo que, quando nenhum argumento é passado, ele efetua um curry real e descontinua a chamada do método com argumentos a favor de uma nova partialfunção. Isso pode parecer um pouco estranho , mas maximizaria a compatibilidade com versões anteriores - já que não há razão para usar aplicativos parciais com zero argumentos - evitando a situação mais feia (IMHO) de ter uma nova função com nome diferente para currying adequado enquanto a função realmente nomeado curryfaz algo diferente e similarmente confuso.

Escusado será dizer que o resultado da chamada curryé completamente diferente do curry real. Se realmente cursou a função, você seria capaz de escrever:

def add = { x, y -> x + y }
def addCurried = add.curry()   // should work like { x -> { y -> x + y } }
def add1 = addCurried(1)       // should work like { y -> 1 + y }
assert add1(1) == 2 

… E funcionaria, porque addCurrieddeveria funcionar como { x -> { y -> x + y } }. Em vez disso, lança uma exceção de tempo de execução e você morre um pouco por dentro.

Jordan Grey
fonte
1
Eu acho que rcurry e ncurry em funções com argumentos> 2 demonstram que isso realmente é apenas uma aplicação parcial, sem currying
jk.
@jk De fato, é demonstrável em funções com argumentos == 2, como observo no final. :)
Jordan Gray
3
@matcauthon Estritamente falando, o "objetivo" de currying é transformar uma função com muitos argumentos em uma cadeia aninhada de funções com um argumento cada. Eu acho que o que você está pedindo é uma razão prática que você deseja usar currying, que é um pouco mais difícil de justificar em Groovy do que em ex LISP ou Haskell. O ponto é que o que você provavelmente deseja usar na maioria das vezes é aplicação parcial, não currying.
Jordan Gray
4
+1 paraand you die a little inside
Thomas Eding
1
Uau, eu entendo curry muito melhor depois de ler sua resposta: +1 e obrigado! Específico para a pergunta, eu gosto do que você disse, "combinando currying com aplicação parcial".
precisa saber é o seguinte
3

Eu acho que é claro que o curry groovy é realmente uma aplicação parcial quando se considera funções com mais de dois argumentos. considerar

f :: (a,b,c) -> d

sua forma ao curry seria

fcurried :: a -> b -> c -> d

no entanto, o curry do groovy retornará algo equivalente a (assumindo chamado com 1 argumento x)

fgroovy :: (b,c) -> d 

que chamará f com o valor de um fixo para x

ou seja, enquanto o curry do groovy pode retornar funções com argumentos N-1, as funções com curry corretamente apenas têm 1 argumento, portanto, o groovy não pode curry com curry

jk.
fonte
2

O Groovy pegou emprestado o nome de seus métodos de curry de várias outras linguagens FP não puras, que também usam nomes semelhantes para aplicação parcial - talvez lamentável por essa funcionalidade centrada em FP. Existem várias implementações de currying "reais" sendo propostas para inclusão no Groovy. Um bom tópico para começar a ler sobre eles está aqui:

http://groovy.markmail.org/thread/c4ycxdzm3ack6xxb

A funcionalidade existente permanecerá de alguma forma e a compatibilidade com versões anteriores será levada em consideração ao fazer uma chamada sobre o nome dos novos métodos, etc. - portanto, não posso dizer nesta fase qual será a nomeação final dos novos / antigos métodos. estar. Provavelmente um comprometimento na nomeação, mas veremos.

Para a maioria dos programadores de OO, a distinção entre os dois termos (currying e aplicação parcial) é discutivelmente amplamente acadêmica; no entanto, quando você estiver acostumado a eles (e quem quiser manter seu código for treinado para ler esse estilo de codificação), a programação no estilo sem ponto ou tácito (que o currying "real" suporta) permite que certos tipos de algoritmos sejam expressos de forma mais compacta e, em alguns casos, de maneira mais elegante. Obviamente, há "beleza nos olhos de quem vê" aqui, mas ter a capacidade de suportar os dois estilos está de acordo com a natureza de Groovy (OO / FP, estática / dinâmica, classes / scripts etc.).

Paul King
fonte
1

Dada esta definição encontrada na IBM:

O termo curry é retirado de Haskell Curry, o matemático que desenvolveu o conceito de funções parciais. Currying refere-se a colocar vários argumentos em uma função que aceita muitos argumentos, resultando em uma nova função que pega os argumentos restantes e retorna um resultado.

halveré sua nova função (ou fechamento), que agora requer apenas um parâmetro. A chamada halver(10)resultaria em 5.

Para isso, transforma uma função com n argumentos em uma função com n-1 argumentos. O mesmo é dito pelo seu exemplo haskell, o que o curry faz.

Matcauthon
fonte
4
A definição da IBM está incorreta. O que eles definem como currying é na verdade aplicação parcial de função, que liga (corrige) argumentos de uma função para criar uma função com menor aridade. O curry transforma uma função que leva vários argumentos em uma cadeia de funções, cada uma recebendo um argumento.
Jordan Gray
1
A definição de wikipedia é a mesma da IBM: em matemática e ciência da computação, currying é a técnica de transformar uma função que recebe vários argumentos (ou uma n-tupla de argumentos) de forma que possa ser chamada de cadeia de funções, cada uma com um único argumento (aplicação parcial). Groovy transforma uma função (com dois argumentos) com a rcurryfunção (que leva um argumento) em uma função (com agora apenas um argumento). Eu vinculei a função curry com um argumento à minha função base para obter minha função resultante.
matcauthon
3
não, a definição da wikipedia é diferente - a aplicação parcial é quando você chama a função curried - e não quando a define, que é o que o groovy faz
jk.
6
@jk está correto. Leia a explicação da Wikipedia novamente e você verá que o que é retornado é uma cadeia de funções com um argumento cada, e não uma função com n - 1argumentos. Veja o exemplo no final da minha resposta; veja também mais adiante neste artigo para saber mais sobre a distinção que está sendo feita. en.wikipedia.org/wiki/…
Jordan Gray
4
É muito significativo, confie em mim. Novamente, o código no final da minha resposta demonstra como uma implementação correta funcionaria; não precisaria de argumentos, por um lado. A implementação atual deve realmente ser nomeada, por exemplo partial.
Jordan Gray