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.