Java는 Currying을 지원합니까?


90

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


4
기록을 위해 Java 8은 이제 커링 및 부분 애플리케이션을 지원하고 클로저에 대한 기본 지원을 제공합니다. 이것은 매우 오래된 질문입니다.
Robert Fischer

답변:


146

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를 정당화하기에 충분하지 않습니다.


11
Java 8에 감명을 받았지만 Clojure는 기능적 언어 기능을 넘어서는 강력한 플랫폼입니다. Clojure는 매우 효율적이고 변경 불가능한 데이터 구조와 소프트웨어 트랜잭션 메모리와 같은 정교한 동시성 기술을 제공합니다.
Michael Easter

2
솔루션에 감사드립니다. 언어 발굴에 대해서는 그다지 많지 않습니다. :) 약한 IDE 지원이 문제이지만 도구 / 라이브러리는 분명하지 않습니다. Clojure 이후 Java 8로 돌아간 후에는 도구 midje, 코어와 같은 라이브러리가 누락되었습니다. .async 및 매크로 및 쉬운 기능 구문과 같은 언어 기능. Clojure에서의 무두질 예 :(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
Korny

5
Clojure는 훌륭한 언어 일 수 있지만 문제는 기존의 C 스타일 구문에만 사용되는 대부분의 Java 개발자에게 너무 "외국 적"이라는 것입니다. 미래에 Clojure (또는 다른 대체 JVM 언어) 로의 중요한 마이그레이션을보기는 매우 어렵습니다. 특히 이러한 여러 언어가 이미 수년 동안 존재하고 발생하지 않았 음을 고려하면 (.NET 세계에서 동일한 현상이 발생합니다. F #과 같은 언어는 한계가 있습니다).
Rogério 2015 년

2
간단한 케이스를 보여주기 때문에 나는 반대 투표를해야합니다. String에서 자신의 클래스를 만든 다음 다른 클래스로 변환 한 다음 코드 양을 비교해보십시오
M4ks

11
@ M4ks 문제는 자바가 커링을 지원하는지 여부 만 다른 언어와 비교할 때 코드의 양에 관한 것이 아닙니다.
Rogério

67

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


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

@OP : 둘 다 실행 가능한 코드 조각이며 ideone.com에서 사용해 볼 수 있습니다.
missingfaktor

16
이 답변은 Java 8 릴리스 이후 구식입니다. 더 간결한 방법은 Rogério의 답변을 참조하십시오.
Matthias Braun

15

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


IMO, 이것은 소스 코드 currying에서 실제로 호출 된 것 Curry.curryn입니다.
Lebecca

13

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

이전 답변 :

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

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


8

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

1
이제 내 예제 (처음부터 개발)를 완료
했으므로 누락 된 코드

1
@missingfaktor - 내 잘못의 culpa)
안드레아스 돌크

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에서는 즉시 사용할 수 있습니다 ;-)


5

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

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

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

4

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;

3

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


3

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

2

Java에서 Currying을 할 수는 있지만 (지원되지 않기 때문에) 추악합니다. Java에서는 일반 루프와 간단한 표현식을 사용하는 것이 더 간단하고 빠릅니다. 카레를 사용할 위치의 예를 게시하면 동일한 작업을 수행하는 대안을 제안 할 수 있습니다.


3
카레는 루프와 어떤 관련이 있습니까?! 질문에 답하기 전에 적어도 용어를 찾아보십시오.
missingfaktor

@missingFaktor, curried 함수는 일반적으로 컬렉션에 적용됩니다. 예를 들어 list2 = list.apply (curriedFunction) curriedFunction이 2 * ?Java에서는 루프를 사용하여 수행합니다.
Peter Lawrey

@Peter : 커링이 아니라 부분적인 적용입니다. 그리고 어느 것도 수집 작업에만 국한되지 않습니다.
missingfaktor

@missingfaktor, 내 요점은; 특정 기능에 얽매이지 않고 한 발 뒤로 물러나 더 넓은 문제를 살펴보면 간단한 해결책이있을 가능성이 큽니다.
Peter Lawrey

@Peter : 질문의 요점에 대해 질문하려면 답변이 아닌 댓글로 의견을 게시해야합니다. (IMHO)
missingfaktor


2

Java 8에서 Currying을 사용할 때의 장점은 고차 함수를 정의한 다음 1 차 함수와 함수 인수를 연결되고 우아한 방식으로 전달할 수 있다는 것입니다.

다음은 미분 함수 인 미적분의 예입니다.

  1. 미분 함수 근사를 (f (x + h) -f (x)) / h로 정의하겠습니다 . 이것은 고차 함수가 될 것입니다
  2. 2 개의 다른 함수 인 1 / x 와 표준화 된 가우스 분포 의 미분을 계산해 봅시다.

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

예, @ Jérôme에 동의합니다. Java 8의 curring은 Scala 또는 기타 함수형 프로그래밍 언어와 같은 표준 방식으로 지원되지 않습니다.

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");
  }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.