Faz groovy chamar aplicação parcial 'currying'?

15

O Groovy tem um conceito que chama de "currying". Aqui está um exemplo do 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 de divide está sendo vinculado ao valor 2. Isso parece uma forma de aplicação parcial.

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

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

Para as pessoas que não usaram o Haskell a , b e c são todos parâmetros genéricos. curry assume uma função com dois argumentos e retorna uma função que usa a e retorna uma função de b para c . Eu adicionei um par extra de colchetes ao tipo para tornar isso mais claro.

Eu entendi mal o que está acontecendo no groovy exemplo ou é meramente chamado de aplicação parcial? Ou, de fato, faz as duas coisas: ou seja, converte divide em uma função curried e, em seguida, aplica parcialmente 2 nessa nova função.

    
por Richard Warburton 14.06.2012 / 11:29
fonte

4 respostas

14

A implementação do curry do Groovy na verdade não é curry em nenhum ponto, nem mesmo nos bastidores. É essencialmente idêntico à aplicação parcial.

Os métodos curry , rcurry e ncurry return a CurriedClosure object que contém os argumentos vinculados. Ele também tem um método getUncurriedArguments (chamado incorretamente - funções curry, não argumentos) que retorna a composição dos argumentos passados para ele com os argumentos ligados.

Quando um encerramento é chamado, ele finalmente chama % métodoinvokeMethod de MetaClassImpl , que verifica explicitamente se o objeto de chamada é uma instância de CurriedClosure . Em caso afirmativo, ele usa ogetUncurriedArguments acima mencionado para 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 pouco inconsistente acima, suspeito que quem escreveu isso tem um bom entendimento conceitual, mas foi talvez um pouco apressado e - como muitas pessoas inteligentes - confraternizando currying com aplicação parcial. Isso é compreensível (veja a resposta de Paul King), se for um pouco desafortunado; será difícil corrigir isso sem quebrar a compatibilidade com versões anteriores.

Uma solução que eu sugeri é sobrecarregar o método curry de tal forma que quando nenhum argumento é passado ele faz currying real e depreciar chamar o método com argumentos em favor de uma nova função partial . Isso pode parecer um pouco estranho , mas maximizaria a compatibilidade com versões anteriores - já que não há motivo para usar o aplicativo parcial com zero argumentos— enquanto evita a situação mais feia (IMHO) de ter uma função nova e com nome diferente para o curry adequado, enquanto a função realmente chamada curry faz algo diferente e confusamente similar.

Escusado será dizer que o resultado de chamar curry é completamente diferente do curry atual. Se realmente curry 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 addCurried deve 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.

    
por 14.06.2012 / 16:52
fonte
3

Eu acho que é claro que o groovy curry é, na verdade, uma aplicação parcial ao considerar funções com mais de dois argumentos. considere

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

sua forma curried 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 irá chamar f com o valor de um fixo para x

i.e. enquanto o curry groovy pode retornar funções com argumentos N-1, as funções curry corretamente só têm 1 argumento, portanto groovy não pode ser curry com curry

    
por 14.06.2012 / 18:25
fonte
1

Dada essa definição encontrada na IBM:

The term curry is taken from Haskell Curry, the mathematician who developed the concept of partial functions. Currying refers to taking multiple arguments into a function that takes many arguments, resulting in a new function that takes the remaining arguments and returns a result.

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

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

    
por 14.06.2012 / 16:43
fonte
1

O Groovy emprestou a nomenclatura de seus métodos de curry de várias outras linguagens FP não-puras que também usam nomenclatura semelhante para aplicação parcial - talvez infeliz para tal funcionalidade centrada em FP. Existem várias implementações de curry "reais" sendo propostas para inclusão no Groovy. Um bom tópico para começar a ler sobre eles está aqui:

link

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 que nomear os novos métodos etc. - então eu não posso dizer nesta fase qual a nomeação final do novo / métodos antigos serão. Provavelmente um compromisso na nomenclatura, mas vamos ver.

Para a maioria dos programadores OO, a distinção entre os dois termos (currying e aplicação parcial) é discutivelmente amplamente acadêmica; entretanto, uma vez que você esteja acostumado com eles (e quem quer que mantenha seu código é treinado para ler este estilo de codificação) então a programação de estilo livre de pontos ou tácita (que suporta curry "real") permite que certos tipos de algoritmos sejam expressos de forma mais compacta e em alguns casos mais elegantemente. Existe obviamente alguma "beleza está nos olhos de quem vê" aqui, mas ter a habilidade de suportar ambos os estilos está de acordo com a natureza do Groovy (OO / FP, estático / dinâmico, classes / scripts etc.).

    
por 15.06.2012 / 02:51
fonte