Groovy chiama 'curry' l'applicazione parziale?


15

Groovy ha un concetto che chiama "curry". 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 ciò che sta succedendo qui è che l'argomentazione della mano destra divideè legata al valore 2. Questa sembra una forma di applicazione parziale.

Il termine curry viene generalmente utilizzato per indicare la trasformazione di una funzione che accetta una serie di argomenti in una funzione che accetta solo un argomento e restituisce un'altra funzione. Ad esempio, ecco il tipo di curryfunzione in Haskell:

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

Per le persone che non hanno utilizzato Haskell a, be csono tutti i parametri generici. curryaccetta una funzione con due argomenti e restituisce una funzione che accetta ae restituisce una funzione da ba c. Ho aggiunto un paio di parentesi in più al tipo per renderlo più chiaro.

Ho frainteso quello che sta succedendo nell'esempio groovy o è semplicemente errata applicazione parziale? O di fatto fa entrambe le cose: vale a dire converti dividein una funzione curry e quindi si applica parzialmente 2a questa nuova funzione.


1
per coloro che non parlano haskell msmvps.com/blogs/jon_skeet/archive/2012/01/30/…
jk.

Risposte:


14

L'implementazione di Groovy in curryrealtà non funziona in nessun momento, anche dietro le quinte. È essenzialmente identico all'applicazione parziale.

Gli curry, rcurrye ncurrymetodi restituiscono un CurriedClosureoggetto che contiene gli argomenti legati. Ha anche un metodo getUncurriedArguments(nome errato: funzioni di curry, non argomenti) che restituisce la composizione degli argomenti passati ad esso con gli argomenti associati.

Quando viene chiamata una chiusura, alla fine chiama il invokeMethodmetodo diMetaClassImpl , che controlla esplicitamente se l'oggetto chiamante è un'istanza di CurriedClosure. In tal caso, utilizza quanto sopra getUncurriedArgumentsper comporre l'intera gamma 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 in qualche modo 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 - si è mescolato al curry con un'applicazione parziale. Questo è comprensibile (vedi la risposta di Paul King), anche se un po 'sfortunato; sarà difficile correggerlo senza interrompere la compatibilità all'indietro.

Una soluzione che ho suggerito è quella di sovraccaricare il currymetodo in modo tale che quando non viene passato alcun argomento faccia un vero curry, e deprecare chiamare il metodo con argomenti a favore di una nuova partialfunzione. Questo potrebbe sembrare un po 'strano , ma massimizzerebbe la retrocompatibilità - poiché non c'è motivo di usare un'applicazione parziale con zero argomenti - evitando la brutta situazione (IMHO) di avere una nuova funzione con un nome diverso per il curry corretto mentre la funzione in realtà named curryfa qualcosa di diverso e confusamente simile.

Va da sé che il risultato della chiamata curryè completamente diverso dall'effettivo curry. Se la funzione fosse davvero curry, saresti 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é addCurrieddovrebbe funzionare come { x -> { y -> x + y } }. Invece genera un'eccezione di runtime e muori un po 'dentro.


1
Penso che rcurry e ncurry su funzioni con argomenti> 2 dimostrino che questa è solo un'applicazione parziale non curry
jk.

@jk In effetti, è dimostrabile sulle funzioni con argomenti == 2, come noto alla fine. :)
Jordan Grey,

3
@matcauthon A rigor di termini, lo "scopo" del curry è quello di trasformare una funzione con molti argomenti in una catena nidificata di funzioni con un argomento ciascuno. Penso che quello che stai chiedendo è una ragione pratica che ci si desidera utilizzare strigliare, che è un po 'più difficile da giustificare in Groovy rispetto, ad esempio LISP o Haskell. Il punto è che probabilmente si desidera utilizzare la maggior parte del tempo è un'applicazione parziale, non curry.
Jordan Grey,

4
+1 perand you die a little inside
Thomas Eding,

1
Wow, ho capito molto meglio il curry dopo aver letto la tua risposta: +1 e grazie! Specifico per la domanda, mi piace che tu abbia detto "curry conflittuale con applicazione parziale".
GlenPeterson

3

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

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

la sua forma al curry sarebbe

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

tuttavia il curry di Groovy restituirà qualcosa di equivalente a (supponendo chiamato con 1 argomento x)

fgroovy :: (b,c) -> d 

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

ad esempio, mentre il curry di Groovy può restituire funzioni con argomenti N-1, le funzioni di curry in modo corretto hanno sempre solo 1 argomento, quindi Groovy non può essere curry con curry


2

Groovy ha preso in prestito la denominazione dei suoi metodi di curry da numerosi altri linguaggi FP non puri che usano anche nomi simili per un'applicazione parziale - forse sfortunato per tale funzionalità incentrata sul FP. Ci sono diverse implementazioni di curry "reali" proposte per l'inclusione in Groovy. Un buon thread per iniziare a leggere su di loro è qui:

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

Le funzionalità esistenti rimarranno in qualche forma e la compatibilità con le versioni precedenti verrà presa in considerazione quando si effettua una chiamata su come nominare i nuovi metodi, ecc. - Quindi non posso dire in questa fase quale sarà la denominazione finale dei nuovi / vecchi metodi essere. 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 siete abituati a loro (e chiunque manterrà il vostro codice è addestrato a leggere questo stile di codifica), allora la programmazione senza punti o tacita (che il "vero" supporto al curry) consente ad alcuni tipi di algoritmi di essere espressi in modo più compatto e in alcuni casi più elegantemente. C'è ovviamente qualche "bellezza sta negli occhi di chi guarda" qui, ma avere la capacità di supportare entrambi gli stili è in linea con la natura di Groovy (OO / FP, statico / dinamico, classi / script ecc.).


1

Data questa definizione trovata su IBM:

Il termine curry è tratto da Haskell Curry, il matematico che ha sviluppato il concetto di funzioni parziali. Currying si riferisce al prendere più argomenti in una funzione che accetta molti argomenti, risultando in una nuova funzione che accetta gli argomenti rimanenti e restituisce un risultato.

halverè la tua nuova funzione (o curry) (o chiusura), che ora accetta solo un parametro. La chiamata halver(10)comporterebbe 5.

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


4
La definizione di IBM non è corretta. Ciò che definiscono curry è in realtà un'applicazione di funzione parziale, che lega (corregge) argomenti di una funzione per creare una funzione con arity più piccola. Currying trasforma una funzione che accetta più argomenti in una catena di funzioni che ciascuna accetta un argomento.
Jordan Grey,

1
In definitiva su Wikipedia è lo stesso di IBM: in matematica e informatica, il curry è la tecnica di trasformazione di una funzione che accetta più argomenti (o una n-tupla di argomenti) in modo tale da poter essere chiamata come catena di funzioni ciascuna con un singolo argomento (applicazione parziale). Groovy trasforma una funzione (con due argomenti) con la rcurryfunzione (che accetta un argomento) in una funzione (con ora solo un argomento). Ho incatenato la funzione curry con un argomento alla mia funzione base per ottenere la funzione risultante.
Matcauthon,

3
no la definizione di Wikipedia è diversa - l'applicazione parziale è quando chiami la funzione curry - non quando la definisci, che è ciò che fa Groovy
jk.

6
@jk è corretto. Leggi di nuovo la spiegazione di Wikipedia e vedrai che ciò che viene restituito è una catena di funzioni con un argomento ciascuno, non una funzione con n - 1argomenti. Vedi l'esempio alla fine della mia risposta; vedi anche più avanti nell'articolo per ulteriori informazioni sulla distinzione in corso. en.wikipedia.org/wiki/…
Jordan Grey

4
È molto significativo, fidati di me. Ancora una volta, il codice alla fine della mia risposta dimostra come funzionerebbe una corretta implementazione; non prenderebbe argomenti, per prima cosa. L'attuale implementazione dovrebbe davvero essere chiamata ad es partial.
Jordan Grey,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.