Groovy chiama l'applicazione parziale 'currying'?

15

Groovy ha un concetto che chiama "currying". Ecco un esempio dal loro wiki:

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

def halver = divide.rcurry(2)

assert halver(8) == 4

La mia comprensione di cosa sta succedendo qui è che l'argomento della mano destra di divide è associato al valore 2. Questa sembra una forma di applicazione parziale.

Il termine currying è solitamente usato per significare trasformare una funzione che prende una serie di argomenti in una funzione che prende solo un argomento e restituisce un'altra funzione. Ad esempio, ecco il tipo della funzione curry in Haskell:

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

Per le persone che non hanno usato Haskell a , b e c sono tutti parametri generici. curry prende una funzione con due argomenti e restituisce una funzione che prende a e restituisce una funzione da b a c . Ho aggiunto una coppia extra di parentesi al tipo per renderlo più chiaro.

Ho frainteso cosa sta succedendo nell'esempio groovy o è semplicemente un errore di applicazione parziale? O in effetti fa entrambe le cose: vale a dire converti divide in una funzione al curry e quindi applica parzialmente 2 a questa nuova funzione.

    
posta Richard Warburton 14.06.2012 - 11:29
fonte

4 risposte

14

L'implementazione di Groovy di curry in realtà non curry in nessun punto, anche dietro le quinte. È essenzialmente identico all'applicazione parziale.

I metodi curry , rcurry e ncurry return un oggetto CurriedClosure che contiene gli argomenti associati. Ha anche un metodo getUncurriedArguments (misnamed-you curry functions, non arguments) che restituisce la composizione degli argomenti passati ad esso con gli argomenti associati.

Quando viene chiamata una chiusura, alla fine chiama invokeMethod metodo di MetaClassImpl , che verifica esplicitamente se l'oggetto chiamante è un'istanza di CurriedClosure . In tal caso, utilizza il summenzionato getUncurriedArguments per comporre l'array completo di argomenti da applicare:

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

Sulla base della nomenclatura confusa e alquanto incoerente di cui sopra, sospetto che chiunque abbia scritto questo abbia una buona comprensione concettuale, ma forse è stato un po 'affrettato e, come molte persone intelligenti, ha confuso il curriculum con un'applicazione parziale. Questo è comprensibile (vedi la risposta di Paul King), se un po 'sfortunato; sarà difficile correggerlo senza rompere la compatibilità con le versioni precedenti.

Una soluzione che ho suggerito è di sovraccaricare il metodo curry in modo tale che quando non viene passato alcun argomento lo fa real currying e deprecate chiamando il metodo con argomenti in favore di una nuova funzione partial . Questo potrebbe sembrare un po 'strano , ma ottimizzerebbe la compatibilità con le versioni precedenti, dal momento che non c'è motivo di utilizzare un'applicazione parziale con argomenti zero- evitando allo stesso tempo la situazione più brutta (IMHO) di avere una nuova funzione con nomi diversi per una corretta elaborazione mentre la funzione effettivamente chiamata curry fa qualcosa di diverso e di confusione simile.

Inutile dire che il risultato della chiamata di curry è completamente diverso da quello attuale. Se davvero curried la funzione, si sarebbe in grado di scrivere:

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 funzionerebbe, perché addCurried dovrebbe funzionare come { x -> { y -> x + y } } . Invece lancia un'eccezione di runtime e muori un po 'dentro.

    
risposta data 14.06.2012 - 16:52
fonte
3

Penso che sia chiaro che groovy curry è in realtà un'applicazione parziale quando si considerano le funzioni con più di due argomenti. considerare

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

la sua forma al curry sarebbe

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

comunque curry di groovy restituirà qualcosa di equivalente a (assumendo chiamato con 1 argomento x)

fgroovy :: (b,c) -> d 

che chiamerà f con il valore di un fisso in x

vale a dire. mentre il curry di groovy può restituire le funzioni con gli argomenti N-1, le funzioni elaborate correttamente hanno sempre un solo argomento, quindi groovy non può essere in corso con curry

    
risposta data 14.06.2012 - 18:25
fonte
1

Data questa definizione trovata in 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 è la tua nuova funzione (chiusa) (o chiusura), che ora richiede solo un parametro. Chiamare halver(10) risulterebbe in 5.

Perciò trasforma una funzione con n argomenti in una funzione con argomenti n-1. Lo stesso è detto dal tuo esempio di haskell che cosa fa curry.

    
risposta data 14.06.2012 - 16:43
fonte
1

Groovy ha preso in prestito la denominazione dei suoi metodi curry da numerosi altri linguaggi FP non puri che usano anche denominazioni simili per applicazioni parziali, forse sfortunate per tale funzionalità FP-centrica. Ci sono diverse implementazioni curry "reali" che vengono proposte per l'inclusione in Groovy. Un buon thread per iniziare a leggerli è qui:

link

La funzionalità esistente rimarrà in una certa forma e verrà presa in considerazione la compatibilità all'indietro quando si effettuerà una chiamata su come denominare i nuovi metodi, ecc. Quindi non posso dire in questa fase quale sarà la denominazione finale del nuovo / i vecchi metodi saranno Probabilmente un compromesso sulla denominazione, ma vedremo.

Per la maggior parte dei programmatori OO la distinzione tra i due termini (curry e applicazione parziale) è probabilmente ampiamente accademica; tuttavia, una volta che vi siete abituati (e chiunque manterrà il vostro codice è addestrato a leggere questo stile di codifica), la programmazione in stile point-free o tacit (supportata da "reali") consente di esprimere alcuni tipi di algoritmi in modo più compatto e in alcuni casi più elegantemente. Ovviamente c'è un po 'di "bellezza negli occhi di chi guarda" ma avere la capacità di supportare entrambi gli stili è in linea con la natura di Groovy (OO / FP, statico / dinamico, classi / script ecc.).

    
risposta data 15.06.2012 - 02:51
fonte

Leggi altre domande sui tag