145

java.util.function.BiFunction が表示されるので、これを実行できます。

BiFunction<Integer, Integer, Integer> f = (x, y) -> { return 0; };

それが十分ではなく、TriFunction が必要な場合はどうすればよいですか? 存在しません!

TriFunction<Integer, Integer, Integer, Integer> f = (x, y, z) -> { return 0; };

I guess I should add that I know I can define my own TriFunction, I'm just trying to understand the rationale behind not including it in the standard library.

4

10 に答える 10

194

TriFunction が必要な場合は、次のようにします。

@FunctionalInterface
interface TriFunction<A,B,C,R> {

    R apply(A a, B b, C c);

    default <V> TriFunction<A, B, C, V> andThen(
                                Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (A a, B b, C c) -> after.apply(apply(a, b, c));
    }
}

次の小さなプログラムは、その使用方法を示しています。結果の型は、最後のジェネリック型パラメーターとして指定されることに注意してください。

  public class Main {

    public static void main(String[] args) {
        BiFunction<Integer, Long, String> bi = (x,y) -> ""+x+","+y;
        TriFunction<Boolean, Integer, Long, String> tri = (x,y,z) -> ""+x+","+y+","+z;


        System.out.println(bi.apply(1, 2L)); //1,2
        System.out.println(tri.apply(false, 1, 2L)); //false,1,2

        tri = tri.andThen(s -> "["+s+"]");
        System.out.println(tri.apply(true,2,3L)); //[true,2,3]
    }
  }

TriFunction の実用的な用途があったか、定義されていたjava.util.*かと思います。java.lang.*ただし、22 個の引数を超えることはありません ;-) つまり、コレクションをストリーミングできるすべての新しいコードは、メソッド パラメーターとして TriFunction を必要としませんでした。そのため、含まれていませんでした。

アップデート

完全を期し、別の回答 (カリー化に関連) の破壊関数の説明に従うために、追加のインターフェイスなしで TriFunction をエミュレートする方法を次に示します。

Function<Integer, Function<Integer, UnaryOperator<Integer>>> tri1 = a -> b -> c -> a + b + c;
System.out.println(tri1.apply(1).apply(2).apply(3)); //prints 6

もちろん、他の方法で機能を組み合わせることができます。

BiFunction<Integer, Integer, UnaryOperator<Integer>> tri2 = (a, b) -> c -> a + b + c;
System.out.println(tri2.apply(1, 2).apply(3)); //prints 6
//partial function can be, of course, extracted this way
UnaryOperator partial = tri2.apply(1,2); //this is partial, eq to c -> 1 + 2 + c;
System.out.println(partial.apply(4)); //prints 7
System.out.println(partial.apply(5)); //prints 8

カリー化は、ラムダ以外の関数型プログラミングをサポートするすべての言語にとって自然なことですが、Java はこのように構築されておらず、達成可能ではありますが、コードの保守が難しく、場合によっては読みにくくなります。ただし、演​​習としては非常に役立ちます。また、部分的な関数がコード内で適切な位置にある場合もあります。

于 2013-10-29T04:07:17.713 に答える
92

As far as I know, there are only two kinds of functions, destructive and constructive.

名前が示すように、建設的な機能は何かを構築しますが、破壊的な機能は何かを破壊しますが、あなたが今考えているような方法ではありません.

たとえば、関数

Function<Integer,Integer> f = (x,y) -> x + y  

建設的なものです。何かを構築する必要があるため。この例では、タプル(x,y)を構築しました。構成関数には、無限の引数を処理できないという問題があります。しかし、最悪の事態は、議論を開いたままにしておくことはできないということです。「まあ、x := 1 にしましょう」と言って、試してみたいすべての y を試すことはできません。タプル全体を毎回構築する必要があり x := 1ます。したがって、関数が何を返すかを見たいy := 1, y := 2, y := 3場合は、 と書く必要がありますf(1,1) , f(1,2) , f(1,3)

Java 8 では、建設的なラムダ関数を使用するメリットがあまりないため、建設的な関数は (ほとんどの場合) メソッド参照を使用して処理する必要があります。これらは静的メソッドに少し似ています。それらを使用することはできますが、実際の状態はありません。

もう 1 つのタイプは破壊的なもので、何かを取り、必要なだけ分解します。たとえば、破壊関数

Function<Integer, Function<Integer, Integer>> g = x -> (y -> x + y) 

f建設的だった機能と同じことをします。破壊的な関数の利点は、ストリームに特に便利な無限の引数を処理できることと、引数を開いたままにしておくことができることです。したがって、 と の場合に結果がどのようになるかを再度確認したい場合は、と は、、x := 1およびの結果であるとy := 1 , y := 2 , y := 3言えます。h = g(1)h(1)y := 1h(2)y := 2h(3)y := 3

これで固定状態になりました!これは非常に動的であり、ほとんどの場合、ラムダに必要なものです。

Factory のようなパターンは、自分に代わって作業を行う関数を入れることができれば、はるかに簡単です。

破壊的なものは互いに簡単に組み合わされます。タイプが正しければ、好きなように構成できます。それを使用すると、(不変の値を使用して) テストをより簡単にするモーフィズムを簡単に定義できます!

建設的なものでもそれを行うことができますが、破壊的な構成はより見栄えがよく、リストまたはデコレーターのように見え、建設的なものはツリーによく似ています。そして、建設的な関数でバックトラックするようなことは、単にいいことではありません. 破壊的な関数 (動的プログラミング) の部分的な関数を保存し、「バックトラック」で古い破壊的な関数を使用するだけです。これにより、コードが大幅に小さくなり、読みやすくなります。構成関数を使用すると、多かれ少なかれすべての引数を覚えておく必要がありますが、これは多くなる可能性があります。

では、なぜ必要がないのかBiFunctionというよりも、なぜ必要があるのTriFunctionでしょうか。

まず第一に、多くの場合、いくつかの値 (3 未満) しかなく、結果だけが必要なので、通常の破壊関数はまったく必要なく、建設的な関数で十分です。そして、建設的な機能を本当に必要とするモナドのようなものがあります。しかし、それを除けば、 が存在する正当な理由はあまりありませんBiFunction。これは、削除する必要があるという意味ではありません。私は死ぬまでモナドのために戦う!

そのため、論理コンテナー クラスに結合できない引数が多数ある場合、および関数を構成的にする必要がある場合は、メソッド参照を使用します。それ以外の場合は、新しく獲得した破壊関数の機能を使用しようとすると、はるかに少ないコード行で多くのことを実行していることに気付くかもしれません。

于 2014-02-15T13:07:59.937 に答える
7

ほぼ同じ質問と部分的な回答があります。建設的/脱構築的な答えが、言語設計者が念頭に置いていたものであるかどうかはわかりません。3 つ以上、最大 N まであると、有効なユースケースがあると思います。

私は .NET 出身です。.NET には、void 関数の Func と Action があります。述語およびその他の特殊なケースも存在します。参照: https://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx

言語設計者が Function、Bifunction を選択し、DecaExiFunction まで継続しなかった理由は何でしょうか?

2 番目の部分の答えは型消去です。コンパイル後、Func と Func の間に違いはありません。したがって、以下はコンパイルされません。

package eu.hanskruse.trackhacks.joepie;

public class Functions{

    @FunctionalInterface
    public interface Func<T1,T2,T3,R>{
        public R apply(T1 t1,T2 t2,T3 t3);
    }

    @FunctionalInterface
    public interface Func<T1,T2,T3,T4,R>{
        public R apply(T1 t1,T2 t2,T3 t3, T4 t4);
    }
}

内部関数は、別の小さな問題を回避するために使用されました。Eclipse は、同じディレクトリ内の Function という名前のファイルに両方のクラスを含めることを主張しました...これが最近のコンパイラの問題かどうかはわかりません。しかし、Eclipseでのエラーを変えることはできません。

Func は、Java Function 型との名前の競合を防ぐために使用されました。

したがって、Func を 3 から 16 までの引数に追加したい場合は、2 つのことを行うことができます。

  • TriFunc、TesseraFunc、PendeFunc、...DecaExiFuncなどを作る
    • (ギリシャ語とラテン語のどちらを使うべきですか?)
  • パッケージ名またはクラスを使用して名前を変えてください。

2 番目の方法の例:

 package eu.hanskruse.trackhacks.joepie.functions.tri;

        @FunctionalInterface
        public interface Func<T1,T2,T3,R>{
            public R apply(T1 t1,T2 t2,T3 t3);
        }

package eu.trackhacks.joepie.functions.tessera;

    @FunctionalInterface
    public interface Func<T1,T2,T3,T4,R>{
        public R apply(T1 t1,T2 t2,T3 t3, T4 t4);
    }

最善のアプローチは何ですか?

上記の例では、andThen() および compose() メソッドの実装を含めませんでした。これらを追加する場合、それぞれ 16 個のオーバーロードを追加する必要があります。TriFunc には、16 個の引数を持つ andthen() が必要です。これにより、循環依存が原因でコンパイル エラーが発生します。また、これらの Function および BiFunction のオーバーロードはありません。したがって、1 つの引数を持つ Func と 2 つの引数を持つ Func も定義する必要があります。.NET では、Java にはない拡張メソッドを使用することで、循環依存関係を回避できます。

于 2016-10-17T17:28:04.430 に答える