¿Java es compatible con Currying?


88

Me preguntaba si hay alguna forma de sacar eso en Java. Creo que no es posible sin soporte nativo para cierres.


4
Para el registro, Java 8 ahora admite la aplicación parcial y el currying y tiene soporte nativo para cierres. Esta es una pregunta tremendamente desactualizada.
Robert Fischer

Respuestas:


145

Java 8 (lanzado el 18 de marzo de 2014) admite currying. El código Java de ejemplo publicado en la respuesta por missingfaktor se puede reescribir como:

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

... lo cual es bastante agradable. Personalmente, con Java 8 disponible, veo pocas razones para usar un lenguaje JVM alternativo como Scala o Clojure. Proporcionan otras características de lenguaje, por supuesto, pero eso no es suficiente para justificar el costo de transición y el soporte más débil de IDE / herramientas / bibliotecas, en mi opinión.


11
Estoy impresionado con Java 8, pero Clojure es una plataforma atractiva más allá de las características funcionales del lenguaje. Clojure ofrece estructuras de datos inmutables y altamente eficientes y técnicas de concurrencia sofisticadas, como la memoria transaccional de software.
Michael Easter

2
Gracias por la solución, no tanto por la excavación del lenguaje :) El soporte de IDE más débil es un problema, pero las herramientas / bibliotecas no están bien definidas: después de haber vuelto a Java 8 después de Clojure, realmente me faltan herramientas midje, bibliotecas como el núcleo .async y características de lenguaje como macros y sintaxis funcional sencilla. El ejemplo de curry en clojure:(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
Korny

5
Clojure puede ser un gran lenguaje, pero el problema es que es demasiado "extraño" para la mayoría de los desarrolladores de Java que solo están acostumbrados a la sintaxis convencional de estilo C; es muy difícil ver una migración significativa a Clojure (o cualquier otro lenguaje JVM alternativo) en el futuro, especialmente considerando que varios de estos lenguajes ya existen durante muchos años y no ha sucedido (el mismo fenómeno ocurre en el mundo .NET, donde lenguajes como F # permanecen marginales).
Rogério

2
Tengo que votar en contra, porque muestra un caso simple. Pruebe uno que de String crea su propia clase que luego se convierte en otra clase y compare la cantidad de código
M4ks

11
@ M4ks La pregunta es solo si Java admite currying o no, no se trata de la cantidad de código en comparación con otros lenguajes.
Rogério

67

La aplicación parcial y currizada es absolutamente posible en Java, pero la cantidad de código requerido probablemente lo desanime.


Algún código para demostrar el curry y la aplicación parcial 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 aquí es el equivalente Haskell del código Java anterior:

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: Ambos son fragmentos de código ejecutables y puede probarlos en ideone.com.
missingfaktor

16
Esta respuesta está desactualizada desde el lanzamiento de Java 8. Vea la respuesta de Rogério para una manera más concisa.
Matthias Braun

15

Hay muchas opciones para Currying con Java 8. El tipo de función Javaslang y jOOλ ofrecen Currying listo para usar (creo que esto fue un descuido en el JDK), y el módulo Cyclops Functions tiene un conjunto de métodos estáticos para Currying JDK Functions y referencias de métodos. P.ej

  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' también está disponible para los consumidores. Ej. Para devolver un método con 3 params, y 2 de los ya aplicados hacemos algo similar a esto

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

Javadoc


En mi opinión, esto es lo que realmente se llama curryingen el Curry.curryncódigo fuente.
Lebecca

13

EDITAR : A partir de 2014 y Java 8, la programación funcional en Java ahora no solo es posible, sino que tampoco es fea (me atrevo a decir que hermosa). Vea, por ejemplo, la respuesta de Rogerio .

Respuesta anterior:

Java no es la mejor opción si va a utilizar técnicas de programación funcional. Como escribió missingfaktor, tendrá que escribir una gran cantidad de código para lograr lo que desea.

Por otro lado, no está restringido a Java en JVM; puede usar Scala o Clojure, que son lenguajes funcionales (Scala es, de hecho, funcional y OO).


8

Curry requiere devolver una función . Esto no es posible con java (sin punteros de función) pero podemos definir y devolver un tipo que contiene un método de función:

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

Ahora vamos a curry en una simple división. Necesitamos un divisor :

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

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

Ahora podemos hacer una división al curry:

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

1
Ahora que terminé mi ejemplo (desarrollado desde cero) resulta que la única diferencia con la respuesta de los códigos faltantes es que no uso clases anónimas;)
Andreas Dolk

1
@missingfaktor - mea culpa;)
Andreas Dolk

5

Bueno, Scala , Clojure o Haskell (o cualquier otro lenguaje de programación funcional ...) son definitivamente LOS lenguajes a utilizar para curry y otros trucos funcionales.

Habiendo dicho eso, es ciertamente posible curry con Java sin las super cantidades de repetición que uno podría esperar (bueno, tener que ser explícito sobre los tipos duele mucho, solo eche un vistazo al curriedejemplo ;-)).

Las pruebas de abajo exhibir tanto, currying una 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);
}

así como la aplicación parcial , aunque no es realmente seguro en este ejemplo:

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

Esto está tomado de una prueba de concepto que acabo de implementar por diversión antes de JavaOne mañana en una hora "porque estaba aburrido" ;-) El código está disponible aquí: https://github.com/ktoso/jcurry

La idea general podría expandirse a FunctionN => FunctionM, con relativa facilidad, aunque la "seguridad de tipos real" sigue siendo un problema para el ejemplo de aplicación de partia y el ejemplo de currying necesitaría una gran cantidad de código repetitivo en jcurry , pero es factible.

En general, es factible, pero en Scala está listo para usar ;-)


5

Se puede emular el curry 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í, mira el ejemplo de código por ti mismo:

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

    }

}

Este es un ejemplo simple en el que curriedAdd es una función de curry que devuelve otra función, y esto se puede usar para la aplicación parcial de parámetros almacenados en curried, que es una función en sí misma. Esto ahora se aplica completamente cuando lo imprimimos en la pantalla.

Además, más adelante puede ver cómo puede usarlo en una especie de estilo JS como

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

4

Una versión más de las posibilidades 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;

También puede definir métodos de utilidad como este:

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

Lo que le brinda una sintaxis posiblemente más legible:

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

3

Siempre es posible cursar un método en Java, pero no lo admite de forma estándar. Tratar de lograr esto es complicado y hace que el código sea bastante ilegible. Java no es el lenguaje apropiado para esto.


3

Otra opción está aquí para 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);
}

entonces podrías lograr curry de esta manera

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

Si bien puede hacer Currying en Java, es feo (porque no es compatible) En Java es más simple y rápido usar bucles simples y expresiones simples. Si publica un ejemplo de dónde usaría curry, podemos sugerir alternativas que hacen lo mismo.


3
¿Qué tiene que ver el curry con los bucles? Al menos busque el término antes de responder una pregunta al respecto.
missingfaktor

@missingFaktor, las funciones curry se suelen aplicar a las colecciones. por ejemplo, list2 = list.apply (curriedFunction) donde curriedFunction podría estar 2 * ?En Java, haría esto con un bucle.
Peter Lawrey

@Peter: Eso es una aplicación parcial, no un curry. Y tampoco es específico de las operaciones de cobranza.
missingfaktor

@missingfaktor, mi punto es; no para obsesionarse con una característica específica, sino dar un paso atrás y mirar el problema más amplio y es muy probable que haya una solución simple.
Peter Lawrey

@Peter: Si desea cuestionar el punto de la pregunta, debe publicar su comentario como un comentario y no como una respuesta. (En mi humilde opinión)
missingfaktor

2

Esta es una biblioteca para curry y aplicación parcial en Java:

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

También admite la desestructuración de Tuples y Map.Entry en parámetros de método, como por ejemplo pasar un Map.Entry a un método que toma 2 parámetros, por lo que Entry.getKey () irá al primer parámetro y Entry.getValue () irá por el segundo parámetro

Más detalles en el archivo README


2

La ventaja de usar Currying en Java 8 es que le permite definir funciones de alto orden y luego pasar una función de primer orden y argumentos de función de una manera elegante y encadenada.

A continuación se muestra un ejemplo de Cálculo, la función derivada.

  1. Definamos la aproximación de la función derivada como (f (x + h) -f (x)) / h . Esta será la función de orden superior
  2. Calculemos la derivada de 2 funciones diferentes, 1 / x , y la distribución gaussiana estandarizada

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í, estoy de acuerdo con @ Jérôme, curring en Java 8 no es compatible de forma estándar como en Scala u otros lenguajes de programación funcional.

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");
  }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.