Java 8: Di mana TriFunction (dan kin) di java.util.function? Atau apa alternatifnya?

113

Saya melihat java.util.function.BiFunction, jadi saya bisa melakukan ini:

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

Bagaimana jika itu tidak cukup baik dan saya membutuhkan TriFungsi? Itu tidak ada!

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

Saya kira saya harus menambahkan bahwa saya tahu saya dapat mendefinisikan TriFungsi saya sendiri, saya hanya mencoba memahami alasan di balik tidak memasukkannya ke dalam pustaka standar.

Richard Finegan
sumber
1
dengan antarmuka bifunction, Anda dapat dengan mudah mendefinisikan kelas N-function, jika Anda mendefinisikan trifungsi sebagai antarmuka terpisah, sb pertama akan bertanya mengapa tidak quadofunction, dan kedua, Anda perlu menduplikasi semua metode yang menggunakan Bifunction sebagai parameter
user902383
6
Ada titik pengembalian yang berkurang untuk API seperti ini. (Secara pribadi, saya pikir JDK8 lulus beberapa waktu yang lalu, tetapi ini bahkan melebihi itu.)
Louis Wasserman
Saya percaya alasannya adalah untuk mengatakan bahwa Fungsi dan BiFungsi sepenuhnya diimplementasikan dengan objek dan tipe asli. Termasuk TriFunctions dengan semua variasi yang berbeda akan meledakkan JRE dengan kelas dan metode.
Thorbjørn Ravn Andersen
1
Jawaban singkat. Di Java, jika Anda tidak melihatnya, Anda membuatnya sendiri (lihat jawaban Alex P tentunya). Sidenote, di C #, pelaksana dotnet memberi Anda pra-kaleng (hingga 16 argumen), tetapi tanpa nama awalan ("Bi" di sini): lihat docs.microsoft.com/en-us/dotnet/api/… Hanya "Func" sederhana. Jadi ini adalah salah satu tempat saya lebih suka dotnet daripada java. Tolong jangan mengubah bagian komentar ini menjadi perang yang menyenangkan. dan batasi komentar hanya untuk BiFunction.
granadaCoder

Jawaban:

81

Sejauh yang saya tahu, hanya ada dua macam fungsi, destruktif dan konstruktif.

Sementara fungsi konstruktif, seperti namanya, mengkonstruksi sesuatu, fungsi destruktif menghancurkan sesuatu, tetapi tidak dengan cara yang mungkin Anda pikirkan sekarang.

Misalnya fungsinya

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

adalah salah satu yang konstruktif . Seperti yang Anda butuhkan untuk membangun sesuatu. Dalam contoh Anda membuat tupel (x, y) . Fungsi konstruktif memiliki masalah, karena tidak mampu menangani argumen yang tak terbatas. Tetapi yang terburuk adalah, Anda tidak bisa membiarkan argumen tetap terbuka. Anda tidak bisa hanya mengatakan "baiklah, biarkan x: = 1" dan coba setiap y yang mungkin ingin Anda coba. Anda harus membangun setiap kali seluruh tupel dengan x := 1. Jadi jika Anda ingin melihat apa fungsi kembali untuk y := 1, y := 2, y := 3Anda harus menulis f(1,1) , f(1,2) , f(1,3).

Di Java 8, fungsi konstruktif harus ditangani (sebagian besar waktu) dengan menggunakan referensi metode karena tidak banyak keuntungan menggunakan fungsi lambda konstruktif. Mereka agak seperti metode statis. Anda dapat menggunakannya, tetapi tidak memiliki status nyata.

Jenis lainnya adalah yang merusak, ia mengambil sesuatu dan membongkar sejauh yang dibutuhkan. Misalnya fungsi destruktif

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

melakukan hal yang sama dengan fungsi fyang konstruktif. Manfaat dari fungsi destruktif adalah, Anda sekarang dapat menangani argumen tak terbatas, yang sangat nyaman untuk aliran, dan Anda dapat membiarkan argumen tetap terbuka. Jadi jika Anda ingin melihat lagi seperti apa hasilnya jika x := 1dan y := 1 , y := 2 , y := 3, Anda dapat mengatakan h = g(1)dan h(1)merupakan hasil untuk y := 1, h(2)untuk y := 2dan h(3)untuk y := 3.

Jadi di sini Anda memiliki keadaan tetap! Itu cukup dinamis dan seringkali itulah yang kami inginkan dari lambda.

Pola seperti Pabrik jauh lebih mudah jika Anda bisa menggunakan fungsi yang bekerja untuk Anda.

Yang merusak mudah digabungkan satu sama lain. Jika jenisnya benar, Anda dapat membuatnya sesuka Anda. Dengan menggunakan itu, Anda dapat dengan mudah menentukan morfisme yang membuat pengujian (dengan nilai yang tidak dapat diubah) jauh lebih mudah!

Anda juga dapat melakukannya dengan yang konstruktif, tetapi komposisi yang merusak terlihat lebih bagus dan lebih seperti daftar atau dekorator, dan yang konstruktif terlihat sangat mirip dengan pohon. Dan hal-hal seperti mundur dengan fungsi konstruktif tidak baik. Anda dapat menyimpan sebagian fungsi dari fungsi destruktif (pemrograman dinamis), dan pada "backtrack" gunakan saja fungsi destruktif lama. Itu membuat kode jauh lebih kecil dan lebih mudah dibaca. Dengan fungsi konstruktif Anda memiliki lebih banyak atau lebih sedikit untuk mengingat semua argumen, yang bisa jadi banyak.

Jadi mengapa ada kebutuhan BiFunctionharus lebih banyak pertanyaan daripada mengapa tidak ada TriFunction?

Pertama-tama, sering kali Anda hanya memiliki sedikit nilai (kurang dari 3) dan hanya memerlukan hasil, jadi fungsi destruktif yang normal tidak akan diperlukan sama sekali, fungsi konstruktif sudah cukup. Dan ada hal-hal seperti monad yang sangat membutuhkan fungsi konstruktif. Tapi selain itu, sebenarnya tidak ada banyak alasan bagus mengapa ada BiFunction. Yang tidak berarti itu harus dihapus! Saya berjuang untuk Monads saya sampai saya mati!

Jadi, jika Anda memiliki banyak argumen, yang tidak dapat Anda gabungkan ke dalam kelas container yang logis, dan jika Anda membutuhkan fungsi yang konstruktif, gunakan referensi metode. Jika tidak, cobalah menggunakan kemampuan baru yang diperoleh dari fungsi destruktif, Anda mungkin menemukan diri Anda melakukan banyak hal dengan baris kode yang jauh lebih sedikit.

pengguna3003859
sumber
2
Anda menjawab pertanyaan saya ... Saya rasa ... Saya tidak tahu apakah desainer bahasa java berasal dari garis pemikiran ini, tetapi saya tidak berpengalaman dalam pemrograman fungsional. Terima kasih atas penjelasannya.
Richard Finegan
79
Saya belum pernah melihat istilah konstruktif dan destruktif digunakan untuk merujuk pada konsep yang Anda gambarkan. Menurut saya kari dan non-kari adalah istilah yang lebih umum.
Feuermurmel
17
Contoh fungsi pertama tidak benar secara sintaksis. Ini harus BiFungsi dan bukan Fungsi, karena membutuhkan dua argumen masukan.
annouk
3
IMO BiFunctiondibuat untuk memudahkan reduksi data, dan kebanyakan Streamoperasi terminal hanyalah reduksi data. Contoh yang baik adalah BinaryOperator<T>, digunakan di banyak orang Collectors. Elemen pertama dikurangi dengan elemen kedua, kemudian dapat dikurangi dengan elemen berikutnya, dan seterusnya. Tentu saja, Anda dapat membuat sebuah Function<T, Function<T, T>func = x -> (y -> / * kode reduksi di sini * /). Tapi, serius? Semua ini dapat Anda lakukan dengan mudah BinaryOperator<T> func = (x, y) -> /*reduction code here*/. Plus, pendekatan pengurangan data ini tampaknya sangat mirip dengan pendekatan "destruktif" Anda bagi saya.
FBB
32
Bagaimana ini mendapatkan begitu banyak suara positif? Itu jawaban yang buruk dan membingungkan, karena didasarkan pada premis bahwa Function<Integer,Integer> f = (x,y) -> x + yJava yang valid, padahal sebenarnya tidak. Itu seharusnya menjadi BiFungsi untuk memulai!
wvdz
162

Jika Anda membutuhkan TriFungsi, lakukan ini:

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

Mengikuti program kecil menunjukkan bagaimana itu dapat digunakan. Ingatlah bahwa tipe hasil ditentukan sebagai parameter tipe generik terakhir.

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

Saya kira jika ada penggunaan praktis untuk TriFunction di java.util.*atau java.lang.*itu akan ditentukan. Saya tidak akan pernah melampaui 22 argumen ;-) Yang saya maksud dengan itu, semua kode baru yang memungkinkan untuk mengalirkan koleksi tidak pernah memerlukan TriFungsi sebagai salah satu parameter metode. Jadi tidak disertakan.

MEMPERBARUI

Untuk kelengkapan dan mengikuti penjelasan fungsi destruktif di jawaban lain (terkait kari), berikut cara TriFungsi bisa ditiru tanpa antarmuka tambahan:

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

Tentu saja, dimungkinkan untuk menggabungkan fungsi dengan cara lain, misalnya:

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

Meskipun kari akan menjadi hal alami untuk bahasa apa pun yang mendukung pemrograman fungsional di luar lambda, Java tidak dibangun dengan cara ini dan, meskipun dapat dicapai, kodenya sulit dipelihara, dan terkadang dibaca. Namun, ini sangat membantu sebagai latihan, dan terkadang fungsi parsial memiliki tempat yang tepat dalam kode Anda.

Alex Pakka
sumber
6
Terima kasih atas solusinya. Dan ya, pasti ada penggunaan untuk BiFungsi, TriFungsi, ... Jika tidak, orang tidak akan mencarinya. Sepertinya semua lambda terlalu baru untuk Oracle saat ini dan akan diperpanjang di versi Java yang lebih baru. Saat ini, ini lebih merupakan bukti konsep.
Stefan Endrullis
Hy @Alex dapatkah Anda menentukan baris berikut. apa yang terjadi di sini default <V> TriFunction <A, B, C, V> andThen (Function <? super R,? extends V> after) {Objects.requireNonNull (after); kembali (A a, B b, C c) -> after.apply (terapkan (a, b, c)); }
Muneeb Nasir
@MuneebNasir - ini memungkinkan Anda melakukan komposisi fungsi: 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); //12Lihat stackoverflow.com/questions/19834611/…
Alex Pakka
Menambahkan andThen()contoh penggunaan ke jawabannya.
Alex Pakka
Tidak hanya kari yang tidak diadaptasi dengan baik ke bahasa Java, tetapi juga, perbaiki saya jika saya salah, tetapi BiFunctiondigunakan di StreamAPI untuk melakukan pengurangan data, yang sangat mirip dengan pendekatan kari bagi saya: Anda tidak pernah mengambil lebih dari dua argumen, dan Anda dapat memproses sejumlah elemen, satu pengurangan pada satu waktu (lihat komentar saya tentang jawaban yang diterima, saya akan senang mengetahui jika saya salah melihatnya seperti itu).
FBB
13

Alternatifnya adalah, tambahkan dependensi di bawah ini,

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

Sekarang, Anda dapat menggunakan Fungsi Vavr, seperti di bawah ini hingga 8 argumen,

3 argumen:

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

5 argumen:

Function5<Integer, Integer, Integer, Integer, Integer, Integer> f = 
      (a, b, c, d, e) -> a + b + c + d + e;
Amol Damodar
sumber
2
Saya akan memperbarui jawaban saya untuk menyebutkan vavr, tetapi Anda yang pertama, jadi saya memberi suara positif. Jika Anda sampai pada titik Anda membutuhkan TriFungsi, ada kemungkinan besar Anda akan menjadi lebih baik dengan menggunakan vavrperpustakaan - itu membuat pemrograman gaya fungsional dapat ditanggung di Java sebanyak mungkin.
Alex Pakka
7

Saya Memiliki pertanyaan yang hampir sama dan jawaban parsial. Tidak yakin apakah jawaban konstruktif / dekonstruktif adalah yang ada dalam pikiran desainer bahasa. Saya pikir memiliki 3 dan lebih upto N memiliki kasus penggunaan yang valid.

Saya berasal dari .NET. dan di .NET Anda memiliki Func dan Action untuk fungsi void. Predikat dan beberapa kasus khusus lainnya juga ada. Lihat: https://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx

Saya bertanya-tanya apa alasan mengapa desainer bahasa memilih Function, Bifunction dan tidak melanjutkan hingga DecaExiFunction?

Jawaban untuk bagian kedua adalah penghapusan tipe. Setelah kompilasi tidak ada perbedaan antara Func dan Func. Oleh karena itu, berikut ini tidak dapat dikompilasi:

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

Fungsi dalam digunakan untuk menghindari masalah kecil lainnya. Eclipse bersikeras memiliki kedua kelas dalam file bernama Fungsi di direktori yang sama ... Tidak yakin apakah ini masalah kompilator saat ini. Tapi saya tidak bisa mengubah kesalahan di Eclipse.

Func digunakan untuk mencegah bentrokan nama dengan tipe Fungsi java.

Jadi jika Anda ingin menambahkan Func dari 3 hingga 16 argumen Anda dapat melakukan dua hal.

  • Buat TriFunc, TesseraFunc, PendeFunc, ... DecaExiFunc dll
    • (Haruskah saya menggunakan bahasa Yunani atau Latin?)
  • Gunakan nama paket atau kelas untuk membuat nama berbeda.

Contoh cara kedua:

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

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

dan

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

Apa pendekatan terbaik?

Dalam contoh di atas saya tidak menyertakan implementasi untuk metode andThen () dan compose (). Jika Anda menambahkan ini, Anda harus menambahkan masing-masing 16 kelebihan beban: TriFunc harus memiliki andthen () dengan 16 argumen. Itu akan memberi Anda kesalahan kompilasi karena dependensi melingkar. Anda juga tidak akan mengalami kelebihan beban ini untuk Fungsi dan BiFungsi. Oleh karena itu, Anda juga harus mendefinisikan Func dengan satu argumen dan Func dengan dua argumen. Dalam dependensi melingkar NET akan dielakkan dengan menggunakan metode ekstensi yang tidak ada di Jawa.

Hans
sumber
2
Mengapa Anda membutuhkan andThen16 argumen? Hasil dari fungsi di Java adalah nilai tunggal. andThenmengambil nilai ini dan melakukan sesuatu dengannya. Juga, tidak ada masalah dengan penamaan. Nama kelas harus berbeda dan berada dalam file berbeda dengan nama yang sama - mengikuti logika yang ditetapkan oleh pengembang bahasa Java dengan Fungsi dan BiFungsi. Selain itu, semua nama berbeda ini diperlukan jika tipe argumen berbeda. Seseorang dapat membuat VargFunction(T, R) { R apply(T.. t) ... }untuk satu tipe.
Alex Pakka
2

Saya menemukan kode sumber untuk BiFunction di sini:

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

Saya memodifikasinya untuk membuat TriFungsi. Seperti BiFunction, ia menggunakan andThen () dan bukan compose (), jadi untuk beberapa aplikasi yang membutuhkan compose (), ini mungkin tidak sesuai. Seharusnya tidak masalah untuk jenis objek normal. Artikel bagus tentang andThen () dan compose () dapat ditemukan di sini:

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));
    }
}
Vance
sumber
2

Anda juga dapat membuat fungsi Anda sendiri dengan menggunakan 3 parameter

@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
Leandro Maro
sumber
0

Anda tidak selalu bisa berhenti di TriFunction. Terkadang, Anda mungkin perlu memberikan sejumlah n parameter ke fungsi Anda. Kemudian tim dukungan harus membuat QuadFungsi untuk memperbaiki kode Anda. Solusi jangka panjang adalah membuat Objek dengan parameter ekstra dan kemudian menggunakan Fungsi atau BiFungsi yang sudah jadi.

Koushik Roy
sumber