Java prend-il en charge le curry?

88

Je me demandais s'il y avait un moyen de tirer cela en Java. Je pense que ce n'est pas possible sans un support natif des fermetures.

utilisateur855
la source
4
Pour mémoire, Java 8 prend désormais en charge le currying et l'application partielle et prend en charge nativement les fermetures. C'est une question complètement dépassée.
Robert Fischer

Réponses:

145

Java 8 (sorti le 18 mars 2014) prend en charge le curry. L'exemple de code Java publié dans la réponse par missingfaktor peut être réécrit comme suit :

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

... ce qui est plutôt sympa. Personnellement, avec Java 8 disponible, je vois peu de raisons d'utiliser un autre langage JVM tel que Scala ou Clojure. Ils fournissent d'autres fonctionnalités de langage, bien sûr, mais cela ne suffit pas pour justifier le coût de transition et le support IDE / outillage / bibliothèques plus faible, IMO.

Rogério
la source
11
Je suis impressionné par Java 8, mais Clojure est une plate-forme convaincante au-delà des fonctionnalités du langage fonctionnel. Clojure offre des structures de données immuables hautement efficaces et des techniques de concurrence sophistiquées telles que la mémoire transactionnelle logicielle.
Michael Easter
2
Merci pour la solution, pas tant pour le langage dig :) Un support IDE plus faible est un problème, mais les outils / bibliothèques ne sont pas clairs - étant revenu à Java 8 après Clojure, il me manque vraiment des outils midje, des bibliothèques comme core .async, et des fonctionnalités de langage telles que des macros et une syntaxe fonctionnelle simple. L'exemple de curry dans clojure:(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
Korny
5
Clojure est peut-être un excellent langage, mais le problème est qu'il est trop «étranger» pour la majorité des développeurs Java qui ne sont habitués qu'à la syntaxe de style C conventionnelle; il est très difficile de voir une migration significative vers Clojure (ou tout autre langage JVM alternatif) à l'avenir, d'autant plus que plusieurs de ces langages existent déjà depuis de nombreuses années et que cela ne s'est pas produit (le même phénomène se produit dans le monde .NET, où les langages comme F # restent marginaux).
Rogério
2
Je dois voter contre, car cela montre un cas simple. Essayez-en un qui de String crée votre propre classe qui se convertit ensuite en une autre classe, et comparez la quantité de code
M4ks
11
@ M4ks La question est uniquement de savoir si Java prend en charge le currying ou non, il ne s'agit pas de la quantité de code par rapport aux autres langages.
Rogério
67

Le curry et l'application partielle sont tout à fait possibles en Java, mais la quantité de code requise vous désactivera probablement.


Un peu de code pour illustrer le curry et l'application partielle en 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 voici l'équivalent Haskell du code Java ci-dessus:

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
manquant
la source
@OP: Les deux sont des extraits de code exécutables et vous pouvez les essayer sur ideone.com.
missingfaktor
16
Cette réponse est obsolète depuis la sortie de Java 8. Voir la réponse de Rogério pour une manière plus concise.
Matthias Braun
15

Il existe de nombreuses options pour Currying avec Java 8. Type de fonction Javaslang et jOOλ offrant tous deux Currying prêt à l'emploi (je pense que c'était un oubli dans le JDK), et le module Cyclops Functions a un ensemble de méthodes statiques pour Currying JDK Functions et références de méthode. Par exemple

  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» est également disponible pour les consommateurs. Par exemple, pour renvoyer une méthode avec 3 paramètres, et 2 de ceux déjà appliqués, nous faisons quelque chose de similaire à ceci

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

Javadoc

John McClean
la source
IMO, c'est ce qu'on appelle vraiment curryingdans le Curry.curryncode source.
Lebecca
13

EDIT : Depuis 2014 et Java 8, la programmation fonctionnelle en Java est désormais non seulement possible, mais pas non plus laide (j'ose dire beau). Voir par exemple la réponse de Rogerio .

Ancienne réponse:

Java n'est pas le meilleur choix si vous allez utiliser des techniques de programmation fonctionnelle. Comme l'a écrit missingfaktor, vous devrez écrire une assez grande quantité de code pour obtenir ce que vous voulez.

D'autre part, vous n'êtes pas limité à Java sur JVM - vous pouvez utiliser Scala ou Clojure qui sont des langages fonctionnels (Scala est, en fait, à la fois fonctionnel et OO).

Xaerxess
la source
8

Le curry nécessite de retourner une fonction . Ce n'est pas possible avec java (pas de pointeurs de fonction) mais nous pouvons définir et renvoyer un type qui contient une méthode de fonction:

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

Maintenant curry une division simple. Nous avons besoin d'un diviseur :

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

et une fonction de division :

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

Maintenant, nous pouvons faire une division curry:

DivideFunction divide = new DivideFunction();
double result = divide.f(2.).f(1.);  // calculates f(1,2) = 0.5
Andreas Dolk
la source
1
Maintenant que j'ai terminé mon exemple (développé à partir de zéro), il s'avère que la seule différence avec la réponse aux codes manquants est que je n'utilise pas de classes anonymes;)
Andreas Dolk
1
@missingfaktor - mea culpa;)
Andreas Dolk
5

Eh bien, Scala , Clojure ou Haskell (ou tout autre langage de programmation fonctionnel ...) sont définitivement LES langages à utiliser pour le curry et autres astuces fonctionnelles.

Cela dit, il est certainement possible de curry avec Java sans les super quantités de passe-partout à laquelle on pourrait s'attendre (enfin, devoir être explicite sur les types fait beaucoup mal - jetez simplement un coup d'œil à l' curriedexemple ;-)).

Les tests ci-dessous présentent les deux, en curry un Function3en 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);
}

ainsi qu'une application partielle , bien que ce ne soit pas vraiment sécurisé dans cet exemple:

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

Ceci est tiré d'un Proof Of Concept que je viens d'implémenter pour le plaisir avant JavaOne demain dans une heure "parce que je m'ennuyais" ;-) Le code est disponible ici: https://github.com/ktoso/jcurry

L'idée générale pourrait être étendue à FunctionN => FunctionM, relativement facilement, bien que la "sécurité de type réel" reste un problème pour l'exemple d'application partia et que l'exemple de curry aurait besoin de beaucoup de code standard dans jcurry , mais c'est faisable.

Dans l'ensemble, c'est faisable, mais dans Scala, c'est hors de la boîte ;-)

Konrad 'ktoso' Malawski
la source
5

On peut émuler le curry avec 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
    }
}
Thomas Darimont
la source
5

Oui, voyez l'exemple de code par vous-même:

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

    }

}

Ceci est un exemple simple avec curriedAdd étant une fonction curry qui renvoie une autre fonction, et cela peut être utilisé pour une application partielle des paramètres tels que stockés dans curried qui est une fonction en soi. Ceci est maintenant appliqué plus tard complètement lorsque nous l'imprimons à l'écran.

De plus, plus tard, vous pouvez voir comment vous pouvez l'utiliser dans le style JS comme

curriedAdd.apply(1).apply(2) //in Java
//is equivalent to 
curriedAdd(1)(2) // in JS
Rishabh Agarwal
la source
4

Encore une fois sur les possibilités de 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;

Vous pouvez également définir des méthodes utilitaires comme celle-ci:

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

Ce qui vous donne une syntaxe sans doute plus lisible:

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

Currying une méthode est toujours possible en Java, mais il ne la prend pas en charge de manière standard. Essayer d'y parvenir est compliqué et rend le code assez illisible. Java n'est pas le langage approprié pour cela.

Jérôme Verstrynge
la source
3

Un autre choix est ici pour 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);
}

alors vous pourriez réaliser le curry de cette façon

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();
John Lee
la source
2

Bien que vous puissiez faire du curry en Java, c'est moche (car ce n'est pas pris en charge). En Java, il est plus simple et plus rapide d'utiliser des boucles simples et des expressions simples. Si vous publiez un exemple d'utilisation du curry, nous pouvons vous suggérer des alternatives qui font la même chose.

Peter Lawrey
la source
3
Qu'est-ce que le curry a à voir avec les boucles?! Recherchez au moins le terme avant de répondre à une question à ce sujet.
missingfaktor
@missingFaktor, les fonctions curry sont généralement appliquées aux collections. par exemple list2 = list.apply (curriedFunction) où curriedFunction pourrait être 2 * ?En Java, vous le feriez avec une boucle.
Peter Lawrey
@Peter: C'est une application partielle, pas un curry. Et ni l'un ni l'autre n'est spécifique aux opérations de collecte.
missingfaktor
@missingfaktor, mon point est; ne pas rester accroché à une fonctionnalité spécifique, mais prendre du recul et regarder le problème plus large et il y aura très probablement une solution simple.
Peter Lawrey
@Peter: Si vous voulez remettre en question le point de la question, vous devriez poster votre remarque comme un commentaire, et non comme une réponse. (IMHO)
missingfaktor
2

Il s'agit d'une bibliothèque de currying et d'application partielle en Java:

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

Il prend également en charge la déstructuration des tuples et Map.Entry dans les paramètres de méthode, comme par exemple en passant un Map.Entry à une méthode qui prend 2 paramètres, donc Entry.getKey () ira au premier paramètre, et Entry.getValue () ira pour le deuxième paramètre

Plus de détails dans le fichier README

Ahmed Adel Ismail
la source
2

L'avantage d'utiliser Currying dans Java 8 est qu'il vous permet de définir des fonctions d'ordre supérieur, puis de transmettre une fonction de premier ordre et des arguments de fonction d'une manière enchaînée et élégante.

Voici un exemple pour Calculus, la fonction dérivée.

  1. Définissons l'approximation de la fonction dérivée comme (f (x + h) -f (x)) / h . Ce sera la fonction d'ordre supérieur
  2. Calculons la dérivée de 2 fonctions différentes, 1 / x , et la distribution gaussienne standardisée

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
      }
    }
José Luis Soto Posada
la source
0

Oui, je suis d'accord avec @ Jérôme, le traitement en Java 8 n'est pas supporté de manière standard comme dans Scala ou d'autres langages de programmation fonctionnelle.

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");
  }
}
Ajeet
la source