Javaはカリー化をサポートしていますか?


88

それをJavaでプルする方法はないかと思っていました。クロージャーのネイティブサポートなしでは不可能だと思います。


4
記録として、Java 8はカリー化と部分的なアプリケーションをサポートし、クロージャーをネイティブでサポートしています。これは非常に時代遅れの質問です。
Robert Fischer 14年

回答:


145

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は、非常に効率的で不変のデータ構造と、ソフトウェアトランザクションメモリなどの高度な同時実行技術を提供します。
マイケルイースター

2
解決策のおかげで、言語の掘り下げについてはそれほどではありません:)弱いIDEサポートは問題ですが、ツール/ライブラリは明確ではありません-Clojureの後でJava 8に戻ったので、ツールmidje、コアのようなライブラリが本当にありません.async、およびマクロや簡単な関数構文などの言語機能。clojureのカレーの例:(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
Korny

5
Clojureは素晴らしい言語かもしれませんが、問題は、従来のCスタイルの構文にしか慣れていないJava開発者の大多数にとっては、あまりにも「異質」であることです。特に、このようないくつかの言語がすでに長年存在していて、それが起こらなかった(.NETの世界でも同じ現象が発生する)ことを考えると、Clojure(またはその他の代替JVM言語)への大幅な移行を予測するのは非常に困難です。 F#などの言語は限界のままです)。
ホジェリオ

2
単純なケースを示しているので、私は反対票を投じなければなりません。Stringから独自のクラスを作成して他のクラスに変換するコードを
試してみて

11
@ M4ks問題は、Javaがカレーをサポートしているかどうかだけであり、他の言語と比較した場合のコードの量についてではありません。
ホジェリオ

67

Javaでは、カリー化と部分的な適用は絶対に可能ですが、必要なコードの量が多分あなたをオフにします。


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のリリース以降は古くなっています。より簡潔な方法については、ロジェリオの回答を参照してください。
Matthias Braun 14年

15

Java 8でのカリー化には多くのオプションがあります。関数タイプJavaslangとjOOλはどちらもそのままカリー化を提供し(これはJDKでの見落としだったと思います)、Cyclops 関数モジュールには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;
 }

「カレー」は消費者も利用できます。たとえば、3つのパラメータを持つメソッドを返し、そのうち2つはすでに適用されているので、これに似た処理を行います。

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

Javadoc


IMO、これは実際curryingCurry.currynソースコードで呼ばれているものです。
レベッカ

13

編集:2014およびJava 8の時点で、Javaでの関数型プログラミングが可能になっただけでなく、醜くもなくなりました(私はあえて美しいと言っています)。例えばロジェリオの答えを見てください

古い答え:

関数型プログラミング手法を使用する場合、Javaは最良の選択ではありません。missingfaktorが書いたように、あなたが望むものを達成するためにはかなり大量のコードを書かなければならないでしょう。

一方、JVMではJavaに限定されません。関数型言語であるScalaまたはClojureを使用できます(実際、Scalaは関数型とOOの両方です)。


8

カリー化関数を返す必要があります。これはjava(関数ポインターなし)では不可能ですが、関数メソッドを含む型を定義して返すことができます。

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

次に、単純な分割をカレーしましょう。分周器が必要です:

// 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
(ゼロから開発した)例を完成したので、missingcodesの答えとの唯一の違いは匿名クラスを使用しないことです;)
Andreas Dolk

1
@missingfaktor-mea culpa;)
Andreas

5

まあ、Scala、Clojure、Haskell(または他の関数型プログラミング言語)は間違いなくTHE言語ですカレーやその他の関数型トリックに使用するです。

そうは言っても、予想されるほどの定型文がなくても、Javaを使ってカレーを作ることは確かに可能です(まあ、型について明示的にする必要があるので、大変なことになりますcurried。例を見てください;-))。

以下のテストは両方を紹介し、a をカレー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);
}

これは、JavaOneが明日1時間前に「退屈したから」という楽しみのために実装したProof Of Conceptから取られたものです;-)コードは、https//github.com/ktoso/jcurryで入手できます。

一般的なアイデアは、比較的簡単にFunctionN => FunctionMに拡張できますが、partiaアプリケーションの例では「実際の型安全性」が依然として問題であり、カリー化の例では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の可能性についてもう1つ説明します。

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

もう1つの選択肢は、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でカリー化を行うことはできますが、醜いです(サポートされていないため)。Javaでは、単純なループと単純な式を使用する方が簡単で高速です。カレーを使用する場所の例を投稿すると、同じことを行う代替案を提案できます。


3
カレーはループと何の関係があるのですか?それについての質問に答える前に、少なくともその用語を調べてください。
missingfaktor

@missingFaktor、カレー関数は通常、コレクションに適用されます。例:list2 = list.apply(curriedFunction)curriedFunctionが2 * ?Javaの場合、ループでこれを実行します。
Peter Lawrey、

@ピーター:それはカレーではなく、部分的なアプリケーションです。また、どちらも収集操作に固有のものではありません。
missingfaktor

@missingfaktor、私のポイントは; 特定の機能にこだわるのではなく、一歩下がって、より広い問題を確認します。単純な解決策がある可能性が非常に高いです。
Peter Lawrey、

@ピーター:質問の要点を質問したい場合は、回答ではなくコメントとしてコメントを投稿してください。(
IMHO

2

これは、Javaでのカレーと部分的なアプリケーションのためのライブラリです。

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

また、タプルとMap.Entryをメソッドパラメータに分解することもサポートしています。たとえば、Map.Entryを2つのパラメータを取るメソッドに渡すと、Entry.getKey()は最初のパラメータに移動し、Entry.getValue() 2番目のパラメータに行きます

READMEファイルの詳細


2

Java 8でカリー化を使用する利点は、高次関数を定義してから、一次関数と関数の引数をチェーンされたエレガントな方法で渡すことができることです。

以下は微分関数の微積分の例です。

  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に同意します。Java8でのカーリングは、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.