Java supporta il curry?


88

Mi chiedevo se esiste un modo per estrarlo in Java. Penso che non sia possibile senza il supporto nativo per le chiusure.


4
Per la cronaca, Java 8 ora supporta il currying e l'applicazione parziale e ha il supporto nativo per le chiusure. Questa è una domanda decisamente superata.
Robert Fischer

Risposte:


145

Java 8 (rilasciato il 18 marzo 2014) supporta il curry. 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<IntUnaryOperator> 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 utilizzare un linguaggio JVM alternativo come Scala o Clojure. Forniscono altre funzionalità linguistiche, ovviamente, ma questo non è sufficiente per giustificare il costo della transizione e il più debole supporto per IDE / strumenti / librerie, IMO.


11
Sono impressionato da Java 8, ma Clojure è una piattaforma avvincente oltre alle funzionalità del linguaggio funzionale. Clojure offre strutture dati altamente efficienti e immutabili e sofisticate tecniche di concorrenza come la memoria transazionale del software.
Michael Easter,

2
Grazie per la soluzione, non tanto per il linguaggio dig :) Il supporto IDE più debole è un problema, ma gli strumenti / le librerie non sono netti - essendo tornato a Java 8 dopo Clojure, mi mancano davvero gli strumenti midje, librerie come core .async e caratteristiche del linguaggio come macro e sintassi funzionale semplice. L'esempio di curry in clojure:(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
Korny

5
Clojure può essere un ottimo linguaggio, ma il problema è che è troppo "alieno" per la maggior parte degli sviluppatori Java che sono abituati solo alla sintassi in stile C convenzionale; è molto difficile vedere una migrazione significativa a Clojure (o qualsiasi altro linguaggio JVM alternativo) in futuro, specialmente considerando che molti di questi linguaggi esistono già da molti anni e non è successo (lo stesso fenomeno si verifica nel mondo .NET, dove linguaggi come F # rimangono marginali).
Rogério

2
Devo votare in negativo, perché mostra un caso semplice. Provane uno che da String crea la tua classe che poi si converte in un'altra classe e confronta la quantità di codice
M4ks

11
@ M4ks La domanda è solo se Java supporta o meno il curry, non si tratta della quantità di codice rispetto ad altre lingue.
Rogério

67

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


Alcuni codici per dimostrare il currying e l'applicazione parziale in Java:

interface Function1<A, B> {
  public B apply(final A a);
}

interface Function2<A, B, C> {
  public C apply(final A a, final B b);
}

class Main {
  public static Function2<Integer, Integer, Integer> simpleAdd = 
    new Function2<Integer, Integer, Integer>() {
      public Integer apply(final Integer a, final Integer b) {
        return a + b;
      }
    };  

  public static Function1<Integer, Function1<Integer, Integer>> curriedAdd = 
    new Function1<Integer, Function1<Integer, Integer>>() {
      public Function1<Integer, Integer> apply(final Integer a) {
        return new Function1<Integer, Integer>() {
          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<Integer, Integer> adder5 = curriedAdd.apply(5);
    System.out.println(adder5.apply(4));
    System.out.println(adder5.apply(6));
  }
}

FWIW qui è l'equivalente Haskell del codice Java sopra:

simpleAdd :: (Int, Int) -> Int
simpleAdd (a, b) = a + b

curriedAdd :: Int -> Int -> Int
curriedAdd a b = 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

@OP: Entrambi sono frammenti di codice eseguibili e puoi provarli su ideone.com.
missingfaktor

16
Questa risposta è obsoleta dal rilascio di Java 8. Vedere la risposta di Rogério per un modo più conciso.
Matthias Braun

15

Ci sono molte opzioni per il currying con Java 8. Tipo di funzione Javaslang e jOOλ che offrono entrambi Currying out of the box (penso che questa fosse una svista nel JDK), e il modulo Cyclops Functions ha una serie di metodi statici per 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;
 }

Il "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


IMO, questo è ciò che viene realmente chiamato curryingnel Curry.curryncodice sorgente.
Lebecca

13

EDIT : A partire dal 2014 e Java 8, la programmazione funzionale in Java ora non è solo possibile, ma anche non brutta (oso dire bella). Vedi ad esempio la risposta di Rogerio .

Vecchia risposta:

Java non è la scelta migliore, se intendi utilizzare tecniche di programmazione funzionale. Come ha scritto Missingfaktor, dovrai scrivere una grande quantità 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 è, infatti, sia funzionale che OO).


8

Il curry richiede la restituzione di una funzione . Questo non è possibile con java (nessun puntatore a funzione) ma possiamo definire e restituire un tipo che contiene un metodo di funzione:

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

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

// f(X) -> Z
public class Divider implements Function<Double, Double> {
  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<Double, Function<Double, Double>> {
  @Override
  public function<Double, Double> 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

1
Ora che ho finito il mio esempio (sviluppato da zero) risulta che l'unica differenza rispetto alla risposta con codici mancanti è che non uso classi anonime;)
Andreas Dolk

1
@missingfaktor - mea culpa;)
Andreas Dolk

5

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

Detto questo, è certamente possibile curry con Java senza la quantità eccessiva di boilerplate che ci si potrebbe aspettare (beh, dover essere espliciti sui tipi fa molto male però - basta dare un'occhiata curriedall'esempio ;-)).

I test muggiscono vetrina sia, currying un Function3in Function1 => Function1 => Function1:

@Test
public void shouldCurryFunction() throws Exception {
  // given
  Function3<Integer, Integer, Integer, Integer> func = (a, b, c) -> a + b + c;

  // when
  Function<Integer, Function<Integer, Function<Integer, Integer>>> cur = curried(func);

  // then
  Function<Integer, Function<Integer, Integer>> step1 = cur.apply(1);
  Function<Integer, Integer> step2 = step1.apply(2);
  Integer result = step2.apply(3);

  assertThat(result).isEqualTo(6);
}

così come l' applicazione parziale , anche se in questo esempio non è veramente sicuro:

@Test
public void shouldCurryOneArgument() throws Exception {
  // given
  Function3<Integer, Integer, Integer, Integer> adding = (a, b, c) -> a + b + c;

  // when
  Function2<Integer, Integer, Integer> curried = applyPartial(adding, _, _, put(1));

  // then
  Integer got = curried.apply(0, 0);
  assertThat(got).isEqualTo(1);
}

Questo è tratto da una prova di concetto che ho appena implementato per divertimento prima di JavaOne domani tra un'ora "perché ero annoiato" ;-) Il codice è disponibile qui: https://github.com/ktoso/jcurry

L'idea generale potrebbe essere espansa a FunctionN => FunctionM, in modo relativamente semplice, sebbene la "vera sicurezza dei tipi" rimanga un problema per l'esempio dell'applicazione partia e l'esempio currying richiederebbe un sacco di codice boilerplaty in jcurry , ma è fattibile.

Tutto sommato, è fattibile, ma in Scala è fuori dagli schemi ;-)


5

Si può emulare il currying 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
    }
}

5

Sì, guarda tu stesso l'esempio di codice:

import java.util.function.Function;

public class Currying {

    private static Function<Integer, Function<Integer,Integer>> curriedAdd = a -> b -> a+b ;

    public static void main(String[] args) {

        //see partial application of parameters
        Function<Integer,Integer> 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 in cui curriedAdd è una funzione curry che restituisce un'altra funzione, e questa può essere utilizzata per l' applicazione parziale dei parametri come memorizzati in curry che è una funzione in sé. Questo viene ora applicato completamente quando lo stampiamo sullo schermo.

Inoltre, in seguito puoi vedere come puoi usarlo in una sorta di stile JS come

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

4

Un altro approccio alle possibilità di Java 8:

BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;

Function<Integer, Integer> increment = y -> add.apply(1, y);
assert increment.apply(5) == 6;

Puoi anche definire metodi di utilità come questo:

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

Il che ti dà una sintassi probabilmente più leggibile:

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

3

Il curry di un metodo è sempre possibile in Java, ma non lo supporta in modo standard. Cercare di ottenere ciò è complicato e rende il codice abbastanza illeggibile. Java non è il linguaggio appropriato per questo.


3

Un'altra scelta è qui per Java 6+

abstract class CurFun<Out> {

    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<Out> getReadyCurFun() {
        final Out finalResult = getResult();
        return new CurFun<Out>() {
            @Override
            public boolean isReady() {
                return true;
            }
            @Override
            protected CurFun<Out> apply(Object value) {
                return getReadyCurFun();
            }
            @Override
            public Out getResult() {
                return finalResult;
            }
        };
    }

    protected abstract CurFun<Out> apply(final Object value);
}

allora potresti ottenere il curry in questo modo

CurFun<String> curFun = new CurFun<String>() {
    @Override
    protected CurFun<String> apply(final Object value1) {
        return new CurFun<String>() {
            @Override
            protected CurFun<String> apply(final Object value2) {
                return new CurFun<String>() {
                    @Override
                    protected CurFun<String> apply(Object value3) {
                        setResult(String.format("%s%s%s", value1, value2, value3));
//                        return null;
                        return getReadyCurFun();
                    }
                };
            }
        };
    }
};

CurFun<String> recur = curFun.apply("1");
CurFun<String> 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();

2

Anche se 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 useresti il ​​curry, possiamo suggerirti alternative che fanno la stessa cosa.


3
Cosa c'entra il curry con i loop ?! Cerca almeno il termine prima di rispondere a una domanda al riguardo.
missingfaktor

@missingFaktor, le funzioni curry vengono solitamente applicate alle raccolte. es. list2 = list.apply (curriedFunction) dove curriedFunction potrebbe essere 2 * ?In Java lo faresti con un ciclo.
Peter Lawrey

@ Peter: Questa è un'applicazione parziale, non il curry. E nessuno dei due è specifico per le operazioni di raccolta.
missingfaktor

@missingfaktor, Il mio punto è; per non rimanere bloccati su una caratteristica specifica, ma fare un passo indietro e guardare al problema più ampio ed è molto probabile che ci sia una soluzione semplice.
Peter Lawrey

@Peter: Se vuoi mettere in dubbio il punto della domanda, dovresti pubblicare il tuo commento come commento e non come risposta. (IMHO)
missingfaktor

2

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 tuple e Map.Entry nei parametri del metodo, come ad esempio il passaggio di Map.Entry a un metodo che accetta 2 parametri, quindi Entry.getKey () andrà al primo parametro e Entry.getValue () andrà per il secondo parametro

Maggiori dettagli nel file README


2

Il vantaggio dell'utilizzo di Currying in Java 8 è che consente di definire funzioni di ordine elevato e quindi passare una funzione del primo ordine e argomenti di funzione in modo concatenato ed elegante.

Ecco un esempio per Calculus, la funzione derivativa.

  1. Definiamo l'approssimazione della funzione derivativa come (f (x + h) -f (x)) / h . Questa sarà la funzione di ordine elevato
  2. Calcoliamo la derivata di 2 diverse funzioni, 1 / x , e la distribuzione gaussiana standardizzata

1

    package math;

    import static java.lang.Math.*;
    import java.util.Optional;
    import java.util.function.*;

    public class UnivarDerivative
    {
      interface Approximation extends Function<Function<Double,Double>, 
      Function<Double,UnaryOperator<Double>>> {}
      public static void main(String[] args)
      {
        Approximation derivative = f->h->x->(f.apply(x+h)-f.apply(x))/h;
        double h=0.00001f;
        Optional<Double> d1=Optional.of(derivative.apply(x->1/x).apply(h).apply(1.0)); 
        Optional<Double> d2=Optional.of(
        derivative.apply(x->(1/sqrt(2*PI))*exp(-0.5*pow(x,2))).apply(h).apply(-0.00001));
        d1.ifPresent(System.out::println); //prints -0.9999900000988401
        d2.ifPresent(System.out::println); //prints 1.994710003159016E-6
      }
    }

0

Sì, sono d'accordo con @ Jérôme, il curriculum in Java 8 non è supportato in modo standard come in Scala o altri linguaggi di programmazione funzionale.

public final class Currying {
  private static final Function<String, Consumer<String>> MAILER = (String ipAddress) -> (String message) -> {
    System.out.println(message + ":" + ipAddress );
  };
  //Currying
  private static final Consumer<String> LOCAL_MAILER =  MAILER.apply("127.0.0.1");

  public static void main(String[] args) {
      MAILER.apply("127.1.1.2").accept("Hello !!!!");
      LOCAL_MAILER.accept("Hello");
  }
}
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.