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를 정당화하기에 충분하지 않습니다.
(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
커링과 부분적 응용은 자바에서 절대적으로 가능하지만 필요한 코드의 양은 아마 당신을 끌 것입니다.
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);
currying
에서 실제로 호출 된 것 Curry.curryn
입니다.
편집 : 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
예제를 살펴보십시오 ;-)).
시험은, 모두를 전시 노호 무두질Function3
로 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);
}
이 예제에서는 실제로 유형이 안전하지는 않지만 부분 응용 프로그램 도 마찬가지입니다 .
@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
}
}
예, 직접 코드 예제를 참조하십시오.
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
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();
Java에서 Currying을 할 수는 있지만 (지원되지 않기 때문에) 추악합니다. Java에서는 일반 루프와 간단한 표현식을 사용하는 것이 더 간단하고 빠릅니다. 카레를 사용할 위치의 예를 게시하면 동일한 작업을 수행하는 대안을 제안 할 수 있습니다.
2 * ?
Java에서는 루프를 사용하여 수행합니다.
이것은 자바에서 커링 및 부분 응용을위한 라이브러리입니다.
https://github.com/Ahmed-Adel-Ismail/J-Curry
또한 튜플 및 Map.Entry를 메서드 매개 변수로 분해하는 기능을 지원합니다. 두 번째 매개 변수로 이동합니다.
README 파일의 자세한 내용
Java 8에서 Currying을 사용할 때의 장점은 고차 함수를 정의한 다음 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
}
}
예, @ 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");
}
}