web-dev-qa-db-fra.com

Java supporte-t-il le curry?

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

86
user855

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

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 la faiblesse du support IDE/outillage/bibliothèques, IMO.

138
Rogério

Le currying et l'application partielle sont absolument possibles en Java, mais la quantité de code requise vous arrêtera probablement.


n 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 ci-dessus Java code:

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
65
missingfaktor

Il y a beaucoup d'options pour Currying avec Java 8. Type de fonction Javaslang et jOOλ offrant tous deux Currying out of the box (je pense que c'était une erreur dans le JDK), et Cyclopsmodule Fonctions a un ensemble de méthodes statiques pour Currying JDK Fonctions et références de méthodes.

  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 retourner 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

13
John McClean

[~ # ~] modifier [~ # ~] : à partir de 2014 et Java 8, programmation fonctionnelle en = Java est maintenant non seulement possible, mais aussi pas laid (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 fonctionnelles. 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).

13
Xaerxess

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

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

Maintenant, nous allons curry une simple division. 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 un 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);
  }

Maintenant, nous pouvons faire une division au curry:

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

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 auxquelles on pourrait s'attendre (eh bien, avoir à être explicite sur les types fait très mal) - jetez un œil au curried exemple ;-)).

Les tests ci-dessous présentent les deux, currying a Function3 en 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 que application partielle, bien que ce ne soit pas vraiment sûr 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'une preuve de 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é réelle des types" reste un problème pour l'exemple d'application partia et l'exemple de curry aurait besoin de beaucoup de code passe-partout dans jcurry , mais c'est faisable.

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

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
    }
}
5
Thomas Darimont

Une autre prise sur les possibilités 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;
4
Natix

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

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

    }

}

C'est un exemple simple avec curriedAdd étant une fonction curry qui renvoie une autre fonction, et cela peut être utilisé pour application partielle de paramètres tel que stocké dans curried qui est une fonction en soi. Ceci est désormais appliqué plus tard 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
3
Rishabh Agarwal

Curryer 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.

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 pouvez 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();
3
John Lee

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

2
Peter Lawrey

Ceci est une bibliothèque pour le curry et l'application partielle en Java:

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

Il prend également en charge la déstructuration de Tuples et Map.Entry en 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

2
Ahmed Adel Ismail

L'avantage de l'utilisation de Currying dans Java 8 est qu'il vous permet de définir des fonctions d'ordre élevé, puis de passer une fonction de premier ordre et des arguments de fonction de manière chaînée et élégante.

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

  1. Permet de définir l'approximation de la fonction dérivée comme (f (x + h) -f (x))/h. Ce sera la fonction d'ordre élevé
  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
      }
    }
2

Oui, je suis d'accord avec @ Jérôme, se produisant dans Java 8 n'est pas pris en charge de manière standard comme dans Scala ou d'autres langages de programmation fonctionnels).

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