Unterstützt Java Currying?


88

Ich habe mich gefragt, ob es eine Möglichkeit gibt, dies in Java zu erreichen. Ich denke, es ist nicht möglich ohne native Unterstützung für Schließungen.


4
Für die Aufzeichnung unterstützt Java 8 jetzt Currying und Teilanwendung und bietet native Unterstützung für Schließungen. Dies ist eine völlig veraltete Frage.
Robert Fischer

Antworten:


146

Java 8 (veröffentlicht am 18. März 2014) unterstützt das Currying. Der in der Antwort von fehlenden Faktor angegebene Java-Beispielcode kann wie folgt umgeschrieben werden:

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));
   }
}

... was ganz nett ist. Persönlich sehe ich mit Java 8 wenig Grund, eine alternative JVM-Sprache wie Scala oder Clojure zu verwenden. Sie bieten natürlich andere Sprachfunktionen, aber das reicht nicht aus, um die Übergangskosten und die schwächere Unterstützung für IDE / Tools / Bibliotheken, IMO, zu rechtfertigen.


11
Ich bin beeindruckt von Java 8, aber Clojure ist eine überzeugende Plattform, die über funktionale Sprachfunktionen hinausgeht. Clojure bietet hocheffiziente, unveränderliche Datenstrukturen und ausgefeilte Parallelitätstechniken wie Software-Transaktionsspeicher.
Michael Easter

2
Vielen Dank für die Lösung, nicht so sehr für die Sprachausgrabung :) Eine schwächere IDE-Unterstützung ist ein Problem, aber Tools / Bibliotheken sind nicht eindeutig - nachdem ich nach Clojure zu Java 8 zurückgekehrt bin, fehlen mir wirklich Tools midje, Bibliotheken wie Core .async und Sprachfunktionen wie Makros und einfache funktionale Syntax. Das Currying-Beispiel in Clojure:(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
Korny

5
Clojure mag eine großartige Sprache sein, aber das Problem ist, dass es für die Mehrheit der Java-Entwickler, die nur an herkömmliche C-Syntax gewöhnt sind, einfach zu "fremd" ist. Es ist sehr schwierig, in Zukunft eine signifikante Migration zu Clojure (oder einer anderen alternativen JVM-Sprache) zu sehen, insbesondere angesichts der Tatsache, dass mehrere solcher Sprachen bereits seit vielen Jahren existieren und dies nicht geschehen ist (dasselbe Phänomen tritt in der .NET-Welt auf). wo Sprachen wie F # marginal bleiben).
Rogério

2
Ich muss abstimmen, weil es einen einfachen Fall zeigt. Versuchen Sie eine, die aus String Ihre eigene Klasse erstellt, die dann in eine andere Klasse konvertiert wird, und vergleichen Sie die Codemenge
M4ks

11
@ M4ks Die Frage ist nur, ob Java Curry unterstützt oder nicht, es geht nicht um die Menge an Code im Vergleich zu anderen Sprachen.
Rogério

67

Currying und teilweise Anwendung sind in Java absolut möglich, aber die erforderliche Codemenge wird Sie wahrscheinlich ausschalten.


Einige Codes zur Demonstration von Currying und Teilanwendung 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 hier ist das Haskell-Äquivalent des obigen Java-Codes:

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: Beide sind ausführbare Codefragmente und können auf ideone.com ausprobiert werden.
fehlender Faktor

16
Diese Antwort ist seit der Veröffentlichung von Java 8 veraltet. Weitere Informationen finden Sie in Rogérios Antwort.
Matthias Braun

15

Es gibt viele Optionen für das Currying mit Java 8. Die Funktionstypen Javaslang und jOOλ bieten beide Currying out of the box an (ich denke, dies war ein Versehen im JDK), und das Modul Cyclops Functions verfügt über eine Reihe statischer Methoden zum Currying von JDK-Funktionen und Methodenreferenzen. Z.B

  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" ist auch für Verbraucher erhältlich. Um beispielsweise eine Methode mit 3 Parametern zurückzugeben, und 2 der bereits angewendeten, machen wir etwas Ähnliches

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

Javadoc


IMO, das nennt man wirklich curryingim Curry.currynQuellcode.
Lebecca

13

EDIT : Ab 2014 und Java 8 ist funktionale Programmierung in Java nicht nur möglich, sondern auch nicht hässlich (ich wage es schön zu sagen). Siehe zum Beispiel Rogerios Antwort .

Alte Antwort:

Java ist nicht die beste Wahl, wenn Sie funktionale Programmiertechniken verwenden möchten. Wie der fehlende Faktor geschrieben hat, müssen Sie ziemlich viel Code schreiben, um das zu erreichen, was Sie wollen.

Auf der anderen Seite sind Sie in JVM nicht auf Java beschränkt - Sie können Scala oder Clojure verwenden, die funktionale Sprachen sind (Scala ist in der Tat sowohl funktional als auch OO).


8

Beim Currying muss eine Funktion zurückgegeben werden . Dies ist mit Java nicht möglich (keine Funktionszeiger), aber wir können einen Typ definieren und zurückgeben, der eine Funktionsmethode enthält:

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

Lassen Sie uns nun eine einfache Unterteilung vornehmen . Wir brauchen einen Teiler :

// 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;
  }
}

und eine 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);
  }

Jetzt können wir eine Curry-Division machen:

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

1
Nachdem ich mein Beispiel fertiggestellt habe (von Grund auf neu entwickelt), stellt sich heraus, dass der einzige Unterschied zur Antwort auf fehlende Codes darin besteht, dass ich keine anonymen Klassen verwende;)
Andreas Dolk

1
@missingfaktor - mea culpa;)
Andreas Dolk

5

Nun, Scala , Clojure oder Haskell (oder jede andere funktionale Programmiersprache ...) sind definitiv DIE Sprachen , die zum Curryen und für andere funktionale Tricks verwendet werden.

Trotzdem ist es sicherlich möglich, mit Java zu curry, ohne die Super-Mengen an Boilerplate, die man erwarten könnte (nun, es tut sehr weh, explizit über die Typen sprechen zu müssen - schauen Sie sich einfach das curriedBeispiel an ;-)).

Die Tests unten beide präsentieren, currying ein 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);
}

sowie teilweise Anwendung , obwohl es in diesem Beispiel nicht wirklich typsicher ist:

@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);
}

Dies stammt aus einem Proof Of Concept, den ich morgen in einer Stunde vor JavaOne zum Spaß implementiert habe, "weil mir langweilig war" ;-) Der Code ist hier verfügbar: https://github.com/ktoso/jcurry

Die allgemeine Idee könnte relativ einfach auf FunctionN => FunctionM erweitert werden, obwohl "echte Typensicherheit" ein Problem für das partia-Anwendungsbeispiel bleibt und das Currying-Beispiel eine Menge Boilerplaty-Code in jcurry erfordern würde , aber es ist machbar.

Alles in allem ist es machbar, aber in Scala ist es out of the box ;-)


5

Mit Java 7 MethodHandles kann man Currying emulieren: 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

Ja, sehen Sie sich das Codebeispiel an:

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

    }

}

Dies ist ein einfaches Beispiel, bei dem curriedAdd eine Curry-Funktion ist, die eine andere Funktion zurückgibt, und dies kann zur teilweisen Anwendung von Parametern verwendet werden, die in curried gespeichert sind, was eine Funktion für sich ist. Dies wird jetzt später vollständig angewendet, wenn wir es auf dem Bildschirm drucken.

Darüber hinaus können Sie später sehen, wie Sie es in einer Art JS-Stil als verwenden können

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

4

Noch eine Sicht auf die Java 8-Möglichkeiten:

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

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

Sie können auch Dienstprogrammmethoden wie diese definieren:

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

Was Ihnen eine wohl besser lesbare Syntax gibt:

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

3

Das Currying einer Methode ist in Java immer möglich, wird jedoch nicht standardmäßig unterstützt. Der Versuch, dies zu erreichen, ist kompliziert und macht den Code ziemlich unlesbar. Java ist hierfür nicht die geeignete Sprache.


3

Eine andere Wahl ist hier für 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);
}

dann könnten Sie auf diese Weise Currying erreichen

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

Während Sie Currying in Java ausführen können, ist es hässlich (weil es nicht unterstützt wird). In Java ist es einfacher und schneller, einfache Schleifen und einfache Ausdrücke zu verwenden. Wenn Sie ein Beispiel für die Verwendung von Curry veröffentlichen, können wir Alternativen vorschlagen, die dasselbe tun.


3
Was hat Currying mit Loops zu tun?! Schlagen Sie zumindest den Begriff nach, bevor Sie eine Frage dazu beantworten.
fehlender Faktor

@missingFaktor, Curry-Funktionen werden normalerweise auf Sammlungen angewendet. zB list2 = list.apply (curriedFunction) wo curriedFunction sein könnte 2 * ?In Java würden Sie dies mit einer Schleife tun.
Peter Lawrey

@Peter: Das ist eine Teilanwendung, kein Curry. Und beides ist nicht spezifisch für Sammelvorgänge.
fehlender Faktor

@missingfaktor, Mein Punkt ist; Sie sollten sich nicht auf eine bestimmte Funktion festlegen, sondern einen Schritt zurücktreten und sich das umfassendere Problem ansehen. Es ist sehr wahrscheinlich, dass es eine einfache Lösung gibt.
Peter Lawrey

@Peter: Wenn Sie den Punkt der Frage hinterfragen möchten, sollten Sie Ihre Bemerkung als Kommentar und nicht als Antwort veröffentlichen. (IMHO)
fehlender Faktor

2

Dies ist eine Bibliothek für Currying und Teilanwendung in Java:

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

Es unterstützt auch die Destrukturierung von Tupeln und Map.Entry in Methodenparameter, z. B. das Übergeben eines Map.Entry an eine Methode, die zwei Parameter akzeptiert, sodass Entry.getKey () zum ersten Parameter und Entry.getValue () wechselt. wird für den zweiten Parameter gehen

Weitere Details in der README-Datei


2

Der Vorteil der Verwendung von Currying in Java 8 besteht darin, dass Sie Funktionen höherer Ordnung definieren und dann eine Funktion erster Ordnung und Funktionsargumente auf verkettete, elegante Weise übergeben können.

Hier ist ein Beispiel für Calculus, die Ableitungsfunktion.

  1. Definieren wir die Ableitungsfunktionsnäherung als (f (x + h) -f (x)) / h . Dies wird die Funktion höherer Ordnung sein
  2. Berechnen wir die Ableitung von 2 verschiedenen Funktionen, 1 / x , und die standardisierte Gaußsche Verteilung

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

Ja, ich stimme @ Jérôme zu, Curring in Java 8 wird nicht wie in Scala oder anderen funktionalen Programmiersprachen standardmäßig unterstützt.

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");
  }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.