Java supporta Currying?

Mi stavo chiedendo se c’è un modo per farlo in Java. Penso che non sia ansible senza il supporto nativo per le chiusure.

Java 8 (rilasciato il 18 marzo 2014) supporta il currying. Il codice Java di esempio pubblicato nella risposta da missingfaktor può essere riscritto come:

import java.util.function.*; import static java.lang.System.out; // Tested with JDK 1.8.0-ea-b75 public class CurryingAndPartialFunctionApplication { public static void main(String[] args) { IntBinaryOperator simpleAdd = (a, b) -> a + b; IntFunction curriedAdd = a -> b -> a + b; // Demonstrating simple add: out.println(simpleAdd.applyAsInt(4, 5)); // Demonstrating curried add: out.println(curriedAdd.apply(4).applyAsInt(5)); // Curried version lets you perform partial application: IntUnaryOperator adder5 = curriedAdd.apply(5); out.println(adder5.applyAsInt(4)); out.println(adder5.applyAsInt(6)); } } 

… che è abbastanza carino Personalmente, con Java 8 disponibile vedo poche ragioni per usare un linguaggio JVM alternativo come Scala o Clojure. Forniscono altre funzionalità linguistiche, ovviamente, ma ciò non è sufficiente per giustificare il costo di transizione e il più debole supporto IDE / tooling / librerie, IMO.

Il currying e l’applicazione parziale sono assolutamente possibili in Java, ma la quantità di codice richiesta probabilmente ti spegnerà.


Qualche codice per dimostrare il currying e l’applicazione parziale in Java:

 interface Function1 { public B apply(final A a); } interface Function2 { public C apply(final A a, final B b); } class Main { public static Function2 simpleAdd = new Function2() { public Integer apply(final Integer a, final Integer b) { return a + b; } }; public static Function1> curriedAdd = new Function1>() { public Function1 apply(final Integer a) { return new Function1() { public Integer apply(final Integer b) { return a + b; } }; } }; public static void main(String[] args) { // Demonstrating simple `add` System.out.println(simpleAdd.apply(4, 5)); // Demonstrating curried `add` System.out.println(curriedAdd.apply(4).apply(5)); // Curried version lets you perform partial application // as demonstrated below. Function1 adder5 = curriedAdd.apply(5); System.out.println(adder5.apply(4)); System.out.println(adder5.apply(6)); } } 

FWIW qui è l’equivalente di Haskell del sopra codice Java:

 simpleAdd :: (Int, Int) -> Int simpleAdd (a, b) = a + b curriedAdd :: Int -> Int -> Int curriedAdd ab = a + b main = do -- Demonstrating simpleAdd print $ simpleAdd (5, 4) -- Demonstrating curriedAdd print $ curriedAdd 5 4 -- Demostrating partial application let adder5 = curriedAdd 5 in do print $ adder5 6 print $ adder5 9 

EDIT : A partire dal 2014 e Java 8, la programmazione funzionale in Java è ora non solo ansible, ma anche non brutto (oserei dire bello). Vedi ad esempio la risposta di Rogerio .

Vecchia risposta:

Java non è la scelta migliore, se hai intenzione di utilizzare tecniche di programmazione funzionale. Come scrisse missingfaktor, dovrai scrivere una quantità piuttosto grande di codice per ottenere ciò che desideri.

D’altra parte, non sei limitato a Java su JVM: puoi usare Scala o Clojure che sono linguaggi funzionali (Scala è, di fatto, sia funzionale che OO).

Ci sono molte opzioni per Currying con Java 8. Il tipo di funzione Javaslang e jOOλ offrono entrambi Currying out of the box (penso che questa fosse una svista nel JDK), e il modulo Cyclops Functions ha un set di metodi statici per il Currying JDK Functions e riferimenti al metodo. Per esempio

  Curry.curry4(this::four).apply(3).apply(2).apply("three").apply("4"); public String four(Integer a,Integer b,String name,String postfix){ return name + (a*b) + postfix; } 

‘Currying’ è disponibile anche per i consumatori. Ad esempio, per restituire un metodo con 3 parametri, e 2 di quelli già applicati facciamo qualcosa di simile a questo

  return CurryConsumer.curryC3(this::methodForSideEffects).apply(2).apply(2); 

Javadoc

Il currying richiede di restituire una funzione . Questo non è ansible con java (nessun puntatore di funzione), ma possiamo definire e restituire un tipo che contiene un metodo di funzione:

 public interface Function { // intention: f(X) -> Z public Z f(X x); } 

Ora facciamo una semplice divisione. Abbiamo bisogno di un divisore :

 // f(X) -> Z public class Divider implements Function { private double divisor; public Divider(double divisor) {this.divisor = divisor;} @Override public Double f(Double x) { return x/divisor; } } 

e una DivideFunction :

 // f(x) -> g public class DivideFunction implements Function> { @Override public function f(Double x) { return new Divider(x); } 

Ora possiamo fare una divisione al curry:

 DivideFunction divide = new DivideFunction(); double result = divide.f(2.).f(1.); // calculates f(1,2) = 0.5 

Bene, Scala , Clojure o Haskell (o qualsiasi altro linguaggio di programmazione funzionale …) sono sicuramente I linguaggi da usare per curry e altri trucchi funzionali.

Detto questo è certamente ansible curry con Java senza le grandi quantità di boilerplate che ci si potrebbe aspettare (beh, dovendo essere espliciti riguardo ai tipi fa male molto – basta dare un’occhiata all’esempio al curried ;-)).

I test qui sotto mostrano entrambi, inserendo una Function3 in Function1 => Function1 => Function1 :

 @Test public void shouldCurryFunction() throws Exception { // given Function3 func = (a, b, c) -> a + b + c; // when Function>> cur = curried(func); // then Function> step1 = cur.apply(1); Function step2 = step1.apply(2); Integer result = step2.apply(3); assertThat(result).isEqualTo(6); } 

così come l’ applicazione parziale , anche se in questo esempio non è tipicamente corretta:

 @Test public void shouldCurryOneArgument() throws Exception { // given Function3 adding = (a, b, c) -> a + b + c; // when Function2 curried = applyPartial(adding, _, _, put(1)); // then Integer got = curried.apply(0, 0); assertThat(got).isEqualTo(1); } 

Questo è preso da un Proof Of Concept che ho appena implementato per divertimento prima di JavaOne domani in un’ora “perché ero annoiato” 😉 Il codice è disponibile qui: https://github.com/ktoso/jcurry

L’idea generale potrebbe essere estesa a FunctionN => FunctionM, relativamente facilmente, anche se “real typesafety” rimane un problema per l’esempio di applicazione partia e l’esempio di curring avrebbe bisogno di un sacco di codice boilerplaty in jcurry , ma è fattibile.

Tutto sumto, è fattibile, ma in Scala è fuori dagli schemi 😉

Si può emulare il curring con Java 7 MethodHandles: http://www.tutorials.de/threads/java-7-currying-mit-methodhandles.392397/

 import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; public class MethodHandleCurryingExample { public static void main(String[] args) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle sum = lookup.findStatic(Integer.class, "sum", MethodType.methodType(int.class, new Class[]{int.class, int.class})); //Currying MethodHandle plus1 = MethodHandles.insertArguments(sum,0,1); int result = (int) plus1.invokeExact(2); System.out.println(result); // Output: 3 } } 

L’elaborazione di un metodo è sempre ansible in Java, ma non lo supporta in modo standard. Cercare di ottenere questo risultato è complicato e rende il codice piuttosto illeggibile. Java non è la lingua appropriata per questo.

Mentre puoi fare Currying in Java, è brutto (perché non è supportato) In Java è più semplice e veloce usare loop semplici ed espressioni semplici. Se pubblichi un esempio di dove utilizzerai il curriculum, possiamo suggerire alternative che facciano la stessa cosa.

Un’altra scelta è qui per Java 6+

 abstract class CurFun { private Out result; private boolean ready = false; public boolean isReady() { return ready; } public Out getResult() { return result; } protected void setResult(Out result) { if (isReady()) { return; } ready = true; this.result = result; } protected CurFun getReadyCurFun() { final Out finalResult = getResult(); return new CurFun() { @Override public boolean isReady() { return true; } @Override protected CurFun apply(Object value) { return getReadyCurFun(); } @Override public Out getResult() { return finalResult; } }; } protected abstract CurFun apply(final Object value); } 

allora potresti raggiungere il curry in questo modo

 CurFun curFun = new CurFun() { @Override protected CurFun apply(final Object value1) { return new CurFun() { @Override protected CurFun apply(final Object value2) { return new CurFun() { @Override protected CurFun apply(Object value3) { setResult(String.format("%s%s%s", value1, value2, value3)); // return null; return getReadyCurFun(); } }; } }; } }; CurFun recur = curFun.apply("1"); CurFun next = recur; int i = 2; while(next != null && (! next.isReady())) { recur = next; next = recur.apply(""+i); i++; } // The result would be "123" String result = recur.getResult(); 

Sì, guarda l’esempio di codice per te:

 import java.util.function.Function; public class Currying { private static Function> curriedAdd = a -> b -> a+b ; public static void main(String[] args) { //see partial application of parameters Function curried = curriedAdd.apply(5); //This partial applied function can be later used as System.out.println("ans of curried add by partial application: "+ curried.apply(6)); // ans is 11 //JS example of curriedAdd(1)(3) System.out.println("ans of curried add: "+ curriedAdd.apply(1).apply(3)); // ans is 4 } } 

Questo è un semplice esempio con curriedAdd che è una funzione al curry che restituisce un’altra funzione, e questo può essere usato per l’ applicazione parziale di parametri memorizzati in curry che è una funzione in sé. Questo è ora applicato più tardi quando lo stampiamo sullo schermo.

Inoltre, in seguito puoi vedere come puoi usarlo in stile JS come

 curriedAdd.apply(1).apply(2) //in Java //is equivalent to curriedAdd(1)(2) // in JS 

Ancora una possibilità sulle possibilità di Java 8:

 BiFunction add = (x, y) -> x + y; Function increment = y -> add.apply(1, y); assert increment.apply(5) == 6; 

Puoi anche definire metodi di utilità come questo:

 static  Function curry(BiFunction f, A1 a1) { return a2 -> f.apply(a1, a2); } 

Il che ti dà una syntax discutibilmente più leggibile:

 Function increment = curry(add, 1); assert increment.apply(5) == 6; 

Questa è una libreria per il currying e l’applicazione parziale in Java:

https://github.com/Ahmed-Adel-Ismail/J-Curry

Supporta anche la destrutturazione di Tuples e Map.Entry nei parametri del metodo, come ad esempio il passaggio di una Map.Entry a un metodo che richiede 2 parametri, quindi Entry.getKey () andrà al primo parametro e Entry.getValue () andrà per il secondo parametro

Maggiori dettagli nel file README