Java 8:java.util.functionのTriFunction(およびその類)はどこにありますか?または代替は何ですか?


113

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

私は自分のTriFunctionを定義できることを知っていることを追加する必要があると思います。標準ライブラリに含めない理由を理解しようとしているだけです。


1
二機能インターフェースでは、N機能クラスを簡単に定義できます。別のインターフェースとして三機能を定義すると、最初にsbが四機能ではない理由を尋ねられます。次に、二機能をパラメーターとして取るすべてのメソッドを複製する必要があります
user902383

6
このようなAPIの収益が減少するポイントがあります。(個人的には、JDK8はし​​ばらく前に合格したと思いますが、これはそれを超えています。)
Louis Wasserman

その根拠は、FunctionとBiFunctionがオブジェクトとネイティブ型で完全に実装されたということでした。さまざまなバリエーションを持つTriFunctionsを含めると、クラスとメソッドでJREが破壊されます。
するThorbjörnRavnアンデルセン

1
短い答え。Javaでは、表示されない場合は独自に作成します(もちろん、Alex Pの回答を参照してください)。付記、C#では、dotnetの実装者事前に定義されたもの(最大16個の引数)を提供しましたが、プレフィックス名(ここでは「Bi」)はありませんでした。docs.microsoft.com / en -us / dotnet / api /…を参照してください 単純な「Func」。これは、私がjavaよりもdotnetを好む場所の1つです。このコメントセクションをh0ly戦争にしないでください。コメントをBiFunctionのみに制限します。
granadaCoder

回答:


81

私の知る限り、破壊と建設の2種類の機能しかない。

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

たとえば、関数

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

ある建設的な 1。あなたが何かを構築する必要があるので。例では、タプル(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 := 1y := 1 , y := 2 , y := 3、あなたが言うことができるh = g(1)h(1)のための結果でありy := 1h(2)y := 2h(3)y := 3

だからここに固定状態があります!それは非常にダイナミックで、ラムダに求めているのはほとんどの場合です。

Factoryのようなパターンは、あなたに代わって機能する関数を置くことができれば、はるかに簡単です。

破壊的なものは互いに簡単に組み合わせることができます。タイプが正しい場合は、好きなように作成できます。これを使用すると、(不変値を使用して)テストをはるかに容易にする射を簡単に定義できます!

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

それでは、なぜ必要BiFunctionがないのTriFunctionかよりも疑問の余地があるのでしょうか。

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

したがって、論理コンテナークラスに結合できない引数が多数ある場合、および関数を構成的にする必要がある場合は、メソッド参照を使用します。それ以外の場合は、破壊的な関数の新たに得られた機能を使用してみてください。コード行を大幅に減らして多くのことを実行していることに気づくでしょう。


2
あなたは私の質問に答えました...私は思う... Java言語デザイナーがこの考え方から来ているのかどうかはわかりませんが、私は関数型プログラミングに精通していません。ご説明ありがとうございます。
Richard Finegan、2014

79
あなたが説明する概念を参照するために建設的および破壊的な用語が使用されるのを見たことがありません。カレー非カレーの方が一般的な用語だと思います。
Feuermurmel

17
最初の関数の例は構文的に正しくありません。2つの入力引数を取るため、関数ではなくBiFunctionである必要があります。
annouk

3
IMO BiFunctionは簡単なデータ削減を可能にするために作成され、ほとんどStreamの端末操作は単なるデータ削減です。良い例はBinaryOperator<T>、多くで使用されていCollectorsます。最初の要素は2番目の要素で削減され、次の要素で削減できます。もちろん、Function<T, Function<T, T>func = x->(y-> / * reduction code here * /)を作成することもできます。しかし、真剣に?あなたが簡単にできるときのこれらすべてBinaryOperator<T> func = (x, y) -> /*reduction code here*/。さらに、このデータ削減アプローチは、私に対する「破壊的」アプローチによく似ています。
FBB 2017年

32
どうしてこれほど多くの賛成票を得たのですか?Function<Integer,Integer> f = (x,y) -> x + y有効なJavaであるという前提に基づいているため、これは恐ろしくて混乱する答えです。そもそもそれはBiFunctionであるべきです!
wvdz 2018年

162

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はこの方法で構築されておらず、達成可能であるにもかかわらず、コードの保守や読み取りが困難です。ただし、これは演習として非常に役立ちます。また、部分的な関数は、コード内で正当な場所に置かれることがあります。


6
解決策をありがとう。そして、はい、BiFunction、TriFunction、...の使用は間違いなくあります。そうでなければ、人々はそれを検索しません。今のところ、ラムダ全体がOracleにとって新しすぎて、今後のJavaバージョンで拡張されると思われます。現時点では、それはより概念の証明です。
Stefan Endrullis 2014

@Alexさん、次の行を定義してください。ここで何が起こっているのかデフォルト<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)); }
Muneeb Nasir

@MuneebNasir -それはあなたが関数合成を行うことができます:TriFunction<Integer,Integer,Integer,Integer> comp = (x,y,z) -> x + y + z; comp = comp.andThen(s -> s * 2); int result = comp.apply(1, 2, 3); //12参照してくださいstackoverflow.com/questions/19834611/...
アレックスPakka

andThen()回答に使用例を追加しました。
Alex

カリー化はJava言語にうまく適応していないだけでなく、私が間違っている場合は私を修正するだけでなく、データ削減を実行するBiFunctionためにStreamAPIで使用されます。これは、私にとってカリー化アプローチによく似ています。引数、および一度に1つの削減で任意の数の要素を処理できます(受け入れられた回答についての私のコメントを参照してください。私がそのようにそれを見るのが間違っているかどうか私は喜んでお知らせします)。
FBB 2017年

13

代わりに、以下の依存関係を追加し、

<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr</artifactId>
    <version>0.9.0</version>
</dependency>

これで、以下のように、最大​​8つの引数のようにVavr関数を使用できます。

3つの引数:

Function3<Integer, Integer, Integer, Integer> f = 
      (a, b, c) -> a + b + c;

5つの引数:

Function5<Integer, Integer, Integer, Integer, Integer, Integer> f = 
      (a, b, c, d, e) -> a + b + c + d + e;

2
私は答えを更新してvavrについて言及しようとしていましたが、あなたが最初だったので、賛成しました。TriFunctionが必要なポイントに達した場合、vavrライブラリを使用することでより良い結果が得られる可能性が高くなります。これにより、関数型プログラミングがJavaで可能な限り耐えられるようになります。
Alex Pakka

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関数型との名前の衝突を防ぐために使用されました。

したがって、3から16までの引数からFuncを追加する場合は、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()が必要です。循環依存関係があるため、コンパイルエラーが発生します。また、これらの関数とBiFunctionのオーバーロードはありません。したがって、1つの引数を持つFuncと2つの引数を持つFuncも定義する必要があります。.NETでは、Javaには存在しない拡張メソッドを使用することで循環依存関係を回避できます。


2
なぜandThen16の引数が必要なのですか?Javaの関数の結果は単一の値です。andThenこの値を受け取り、何かを行います。また、ネーミングに問題はありません。クラス名は異なる必要があり、同じ名前の異なるファイルに含まれている必要があります-FunctionおよびBiFunctionを使用してJava言語開発者が設定したロジックに従います。また、引数のタイプが異なる場合は、これらすべての異なる名前が必要です。VargFunction(T, R) { R apply(T.. t) ... }単一のタイプのを作成できます。
Alex Pakka 2017年

2

ここでBiFunctionのソースコードを見つけました:

https://github.com/JetBrains/jdk8u_jdk/blob/master/src/share/classes/java/util/function/BiFunction.java

TriFunctionを作成するように変更しました。BiFunctionと同様に、compose()ではなくandThen()を使用するため、compose()を必要とする一部のアプリケーションでは、適切ではない場合があります。通常の種類のオブジェクトには問題ありません。andThen()とcompose()に関する良い記事はここにあります:

http://www.deadcoderising.com/2015-09-07-java-8-functional-composition-using-compose-and-andthen/

import java.util.Objects;
import java.util.function.Function;

/**
 * Represents a function that accepts two arguments and produces a result.
 * This is the three-arity specialization of {@link Function}.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #apply(Object, Object)}.
 *
 * @param <S> the type of the first argument to the function
 * @param <T> the type of the second argument to the function
 * @param <U> the type of the third argument to the function
 * @param <R> the type of the result of the function
 *
 * @see Function
 * @since 1.8
 */
@FunctionalInterface
public interface TriFunction<S, T, U, R> {

    /**
     * Applies this function to the given arguments.
     *
     * @param s the first function argument
     * @param t the second function argument
     * @param u the third function argument
     * @return the function result
     */
    R apply(S s, T t, U u);

    /**
     * Returns a composed function that first applies this function to
     * its input, and then applies the {@code after} function to the result.
     * If evaluation of either function throws an exception, it is relayed to
     * the caller of the composed function.
     *
     * @param <V> the type of output of the {@code after} function, and of the
     *           composed function
     * @param after the function to apply after this function is applied
     * @return a composed function that first applies this function and then
     * applies the {@code after} function
     * @throws NullPointerException if after is null
     */
    default <V> TriFunction<S, T, U, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (S s, T t, U u) -> after.apply(apply(s, t, u));
    }
}

2

3つのパラメーターを取る独自の関数を作成することもできます

@FunctionalInterface
public interface MiddleInterface<F,T,V>{
    boolean isBetween(F from, T to, V middleValue);
}

MiddleInterface<Integer, Integer, Integer> middleInterface = 
(x,y,z) -> x>=y && y<=z; // true

0

常にTriFunctionで停止できるわけではありません。場合によっては、n個のパラメーターを関数に渡す必要があります。次に、サポートチームは、コードを修正するためにQuadFunctionを作成する必要があります。長期的な解決策は、追加のパラメーターを使用してオブジェクトを作成し、既成の関数またはBiFunctionを使用することです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.