programing tip

Java는 Currying을 지원합니까?

itbloger 2020. 9. 13. 10:11
반응형

Java는 Currying을 지원합니까?


Java에서 가져올 방법이 있는지 궁금합니다. 클로저에 대한 기본 지원 없이는 불가능하다고 생각합니다.


Java 8 (2014 년 3 월 18 일 출시)은 커링을 지원합니다. missingfaktor답변에 게시 한 예제 Java 코드는 다음과 같이 다시 작성할 수 있습니다.

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

... 꽤 좋습니다. 개인적으로 Java 8을 사용할 수 있으면 Scala 또는 Clojure와 같은 대체 JVM 언어를 사용할 이유가 거의 없습니다. 물론 다른 언어 기능을 제공하지만 전환 비용과 약한 IDE / 도구 / 라이브러리 지원 인 IMO를 정당화하기에 충분하지 않습니다.


커링과 부분적 응용은 자바에서 절대적으로 가능하지만 필요한 코드의 양은 아마 당신을 끌 것입니다.


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는 위의 Java 코드에 해당하는 Haskell입니다.

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

Currying with Java 8에는 많은 옵션이 있습니다. 함수 유형 Javaslang 및 jOOλ는 모두 즉시 Currying을 제공하며 (JDK에서 감독 한 것으로 생각합니다) Cyclops Functions 모듈 에는 Currying JDK 함수에 대한 정적 메서드 세트가 있습니다. 및 메서드 참조.

  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'을 이용할 수 있습니다. 예를 들어 3 개의 매개 변수가있는 메소드를 반환하고 이미 적용된 매개 변수 중 2 개를 다음과 같이 수행합니다.

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

Javadoc


편집 : 2014 년과 Java 8에서 Java의 함수형 프로그래밍은 이제 가능할뿐만 아니라 추악하지도 않습니다 (감히 아름답다고 말할 수 있습니다). 예를 들어 Rogerio의 답변을 참조하십시오 .

이전 답변 :

함수형 프로그래밍 기술을 사용하려는 경우 Java는 최선의 선택이 아닙니다. missingfaktor가 작성한 것처럼 원하는 것을 달성하려면 상당히 많은 양의 코드를 작성해야합니다.

반면에 JVM의 Java에 국한되지 않고 기능 언어 인 Scala 또는 Clojure 를 사용할 수 있습니다 (사실 Scala는 기능적이며 OO입니다).


Currying함수 를 반환해야 합니다 . 이것은 java (함수 포인터 없음)에서는 가능하지 않지만 함수 메소드를 포함하는 유형을 정의하고 반환 할 수 있습니다.

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

이제 간단한 분할을 카레 합시다 . Divider 가 필요합니다 .

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

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

이제 카레 분할을 할 수 있습니다.

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

음, Scala , Clojure 또는 Haskell (또는 다른 함수형 프로그래밍 언어 ...)은 확실히 커링 및 기타 기능적 트릭에 사용할 언어 입니다.

그렇게 말하면 예상 할 수있는 엄청난 양의 상용구없이 Java로 커리하는 것이 가능합니다 (글쎄, 유형에 대해 명시 적이어야하는 것은 많이 아프지 만 curried예제를 살펴보십시오 ;-)).

시험은, 모두를 전시 노호 무두질Function3Function1 => 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);
}

이 예제에서는 실제로 유형이 안전하지는 않지만 부분 응용 프로그램 도 마찬가지입니다 .

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

이것은 "나는 지루했기 때문에"난 그냥 시간에 내일 자바 원하기 전에 재미로 구현 한 개념 증명에서 가져온 것입니다 ;-) 코드는 여기에 있습니다 : https://github.com/ktoso/jcurry

일반적인 아이디어는 비교적 쉽게 FunctionN => FunctionM으로 확장 될 수 있습니다. "real typesafety"는 partia 애플리케이션 예제에서 여전히 문제가되고 currying 예제는 jcurry 에서 엄청난 양의 보일러 코드가 필요 하지만 가능합니다.

대체로 가능하지만 Scala에서는 즉시 사용할 수 있습니다 ;-)


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

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;

다음과 같은 유틸리티 메서드를 정의 할 수도 있습니다.

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

틀림없이 더 읽기 쉬운 구문을 제공합니다.

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

메서드 커링은 Java에서 항상 가능하지만 표준 방식으로 지원하지 않습니다. 이를 달성하려는 시도는 복잡하고 코드를 읽을 수 없게 만듭니다. Java는 이에 적합한 언어가 아닙니다.


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

이렇게하면 카레를 얻을 수 있습니다

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

예, 직접 코드 예제를 참조하십시오.

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

    }

}

이 간단한 예이다 curriedAdd는 다른 함수를 리턴하는 함수 인 카레, 이것은 사용될 수있는 매개 변수의 일부 응용 에 저장된 카레 자체의 함수이다. 이것은 나중에 화면에 인쇄 할 때 완전히 적용됩니다.

또한 나중에 JS 스타일로 사용하는 방법을 볼 수 있습니다.

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

While you can do Currying in Java, it is ugly (because its not supported) In Java is it simpler and faster to use plain loops and simple expressions. If you post an example of where you would use currying, we can suggest alternatives which do the same thing.


This is a library for currying and partial application in Java :

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

It also supports destructuring Tuples and Map.Entry into method parameters, like for example passing a Map.Entry to a method that takes 2 parameters, so the Entry.getKey() will go to the first parameter, and the Entry.getValue() will go for the second parameter

More details in the README file


The advantage of using Currying in Java 8 is that it lets you define high order functions and then pass a first order function and function arguments in a chained, elegant way.

Here is an example for Calculus, the derivative function.

  1. Lets define the derivative function approximation as (f(x+h)-f(x))/h. This will be the high order function
  2. Let's calculate the derivative of 2 different functions, 1/x, and the standardized gaussian distribution

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

Yes, I agree with @Jérôme, curring in Java 8 is not supported in a standard way like in Scala or other functional programming languages.

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

참고URL : https://stackoverflow.com/questions/6134278/does-java-support-currying

반응형