Pregunta ¿Java es compatible con Currying?


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


73
2018-05-26 05:54


origen


Respuestas:


Java 8 (lanzado el 18 de marzo de 2014) admite currying. El código Java de ejemplo publicado en la respuesta por missingfaktor puede ser reescrito 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));
   }
}

... que es bastante agradable. Personalmente, con Java 8 disponible, veo pocas razones para usar un lenguaje JVM alternativo como Scala o Clojure. Ofrecen otras características del 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, IMO.


122
2018-02-07 11:45



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


Algunos códigos para demostrar el currying 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

60
2018-05-26 06:00



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 es hermosa). Ver 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 missingfaktor escribió, tendrá que escribir una gran cantidad de código para lograr lo que quiere.

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


11
2018-05-26 06:06



Hay muchas opciones para Currying con Java 8. Tipo de función Javaslang y jOOλ ambas ofrecen Currying out of the box (creo que esto fue un descuido en el JDK), y Cíclope  Módulo de funciones 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. Por ejemplo, para devolver un método con 3 params y 2 de los que ya aplicamos hacemos algo similar a este

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

Javadoc


11
2018-06-11 17:05



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

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

Ahora vamos a curry una división simple. Necesitamos una 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 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);
  }

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

5
2018-05-26 06:55



Bien, Scala, Clojure o Haskell (o cualquier otro lenguaje de programación funcional ...) son definitivamente Los idiomas para usar para currying y otros trucos funcionales.

Habiendo dicho eso, es posible curry con Java sin las súper cantidades de repetitivo que cabría esperar (bueno, tener que ser explícito sobre los tipos me duele mucho, solo eche un vistazo al curried ejemplo ;-)).

Las pruebas abajo muestran ambos, zurra un Function3 dentro 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);
}

tanto como 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 "real typesafety" sigue siendo un problema para el ejemplo de la aplicación partia y el ejemplo de currying necesitaría muchísimo código fuente en jcurry, pero es factible

Con todo, es posible, pero en Scala está fuera de la caja ;-)


3
2017-09-21 21:33



Se puede emular el 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
    }
}

3
2018-06-11 18:46



Currying es siempre posible 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.


2
2018-05-26 06:02



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 currying, podemos sugerir alternativas que hagan lo mismo.


1
2018-05-26 06:30



Otra opción es 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 currying 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();

1
2017-11-14 06:09



Una toma más sobre 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 da una sintaxis posiblemente más legible:

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

0
2017-12-08 18:25