Java 8: java.util.function'da TriFunction (ve kin) nerede? Veya alternatif nedir?


113

Java.util.function.BiFunction'ı görüyorum, böylece bunu yapabilirim:

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

Ya bu yeterince iyi değilse ve TriFunction'a ihtiyacım varsa? Mevcut değil!

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

Sanırım kendi TriFunction'ımı tanımlayabileceğimi bildiğimi eklemeliyim, sadece onu standart kitaplığa dahil etmemenin arkasındaki mantığı anlamaya çalışıyorum.


1
iki işlevli arabirim ile kolayca N-işlev sınıfını tanımlayabilirsiniz, eğer üç işlevi ayrı arabirim olarak tanımlarsanız, ilk sb neden dört işlevli olmadığını soracaktır ve ikincisi, B
işlevini

6
Bunun gibi API'ler için azalan bir getiri noktası var. (Şahsen, JDK8'in bir süre önce geçtiğini düşünüyorum, ancak bu bile bunun ötesinde.)
Louis Wasserman

Mantığın, Function ve BiFunction'ın nesneler ve yerel türlerle tamamen uygulandığını söylemek olduğuna inanıyorum. Tüm çeşitli varyasyonlara sahip TriFunctions'ı dahil etmek, JRE'yi sınıflar ve yöntemlerle havaya uçuracaktır.
Thorbjørn Ravn Andersen

1
Kısa cevap. Java'da, görmüyorsanız, kendinizinkini oluşturursunuz (elbette Alex P'nin yanıtlarına bakın). Sidenote, C # 'da, dotnet uygulayıcıları size önceden korunmuş olanları (en fazla 16 argüman) verdi, ancak önek adları olmadan (burada "Bi"): bkz. Docs.microsoft.com/en-us/dotnet/api/… Sadece basit bir "Func". Yani burası java yerine dotnet'i tercih ettiğim yerlerden biri. Lütfen bu yorum bölümünü bir h0ly savaşına dönüştürmeyin. ve yorumları yalnızca BiFunction ile sınırlandırın.
granadaCoder

Yanıtlar:


81

Bildiğim kadarıyla yıkıcı ve yapıcı olmak üzere sadece iki tür işlev vardır.

Yapıcı işlev, adından da anlaşılacağı gibi, bir şeyi inşa ederken, yıkıcı bir şey bir şeyi yok eder, ama şu anda düşündüğünüz şekilde değil.

Örneğin, işlev

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

Bir olan yapıcı bir. Bir şey inşa etmeniz gerektiği gibi. Örnekte demeti oluşturdunuz (x, y) . Yapıcı işlevler, sonsuz argümanları işleyememe sorununa sahiptir. Ama en kötüsü, bir tartışmayı öylece açık bırakamazsın. "Peki, x: = 1" diyemezsiniz ve denemek isteyebileceğiniz her y'yi deneyemezsiniz. Tüm demeti her seferinde inşa etmelisiniz x := 1. Yani fonksiyonların ne getireceğini görmek y := 1, y := 2, y := 3istiyorsanız yazmanız gerekir f(1,1) , f(1,2) , f(1,3).

Java 8'de, yapıcı bir lambda işlevi kullanmanın pek bir avantajı olmadığından, yapıcı işlevler (çoğu zaman) yöntem referansları kullanılarak ele alınmalıdır. Biraz statik yöntemler gibidirler. Onları kullanabilirsiniz, ancak gerçek bir durumları yoktur.

Diğeri ise yıkıcı olanıdır, bir şeyi alır ve gerektiği kadar parçalara ayırır. Örneğin, yıkıcı işlev

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

fyapıcı olan işlevle aynı şeyi yapar . Yıkıcı bir işlevin faydaları, artık sonsuz sayıda argümanın üstesinden gelebilmenizdir, bu özellikle akışlar için uygundur ve argümanları açık bırakabilirsiniz. Tekrar sonuç eğer nasıl olacağını görmek istiyorsanız Yani x := 1ve y := 1 , y := 2 , y := 3şunları söyleyebilirsiniz h = g(1)ve h(1)sonucu şudur y := 1, h(2)için y := 2ve h(3)için y := 3.

Yani burada sabit bir durumunuz var! Bu oldukça dinamik ve çoğu zaman bir lambdadan istediğimiz şey bu.

Factory gibi modeller, işi sizin için yapan bir işlev koyabilirseniz çok daha kolaydır.

Yıkıcı olanlar birbirleriyle kolayca birleştirilir. Yazı doğruysa, onları istediğiniz gibi oluşturabilirsiniz. Bunu kullanarak (değişmez değerlerle) testi çok daha kolay hale getiren morfizmaları kolayca tanımlayabilirsiniz!

Bunu yapıcı biriyle de yapabilirsiniz, ancak yıkıcı kompozisyon daha güzel ve daha çok bir liste veya bir dekoratör gibi görünür ve yapıcı olan bir ağaca çok benzer. Yapıcı işlevlerle geriye dönük izleme gibi şeyler hiç hoş değil. Yıkıcı bir işlevin (dinamik programlama) kısmi işlevlerini kaydedebilir ve "geriye doğru" yalnızca eski yıkıcı işlevi kullanabilirsiniz. Bu, kodu çok daha küçük ve daha okunaklı hale getirir. Yapıcı işlevlerle, tüm argümanları hatırlamak için az ya da çok sahip olursunuz, bu çok fazla olabilir.

Öyleyse neden yoktan çok BiFunctionsoru sorulmasına ihtiyaç var TriFunction?

Her şeyden önce, çoğu zaman sadece birkaç değere sahip olursunuz (3'ten az) ve sadece bir sonuca ihtiyacınız vardır, bu nedenle normal bir yıkıcı işleve hiç gerek kalmaz, yapıcı bir işlev görür. Ve gerçekten yapıcı bir işleve ihtiyaç duyan monadlar gibi şeyler var. Ancak bunun dışında, neden bir tane olduğu konusunda pek çok iyi neden yok BiFunction. Bu, kaldırılması gerektiği anlamına gelmez! Monad'larım için ölene kadar savaşırım!

Dolayısıyla, mantıksal bir konteyner sınıfında birleştiremeyeceğiniz çok sayıda argümanınız varsa ve işlevin yapıcı olması gerekiyorsa, bir yöntem referansı kullanın. Aksi takdirde, yeni kazanılan yıkıcı işlev becerisini kullanmaya çalışın, kendinizi çok daha az kod satırı ile çok şey yaparken bulabilirsiniz.


2
Soruma cevap verdin ... Sanırım ... Java dili tasarımcılarının bu düşünce tarzından gelip gelmediğini bilmiyorum, ama fonksiyonel programlama konusunda çok bilgim yok. Açıklama için teşekkürler.
Richard Finegan

81
Tanımladığınız kavramlara atıfta bulunmak için kullanılan yapıcı ve yıkıcı terimlerini hiç görmedim . Bence curried ve non-curried daha yaygın terimlerdir.
Feuermurmel

17
İlk işlev örneği sözdizimsel olarak doğru değildir. İki girdi argümanı aldığı için Function değil BiFunction olmalıdır.
annouk

3
IMO BiFunction, kolay veri azaltmaya izin vermek için oluşturulmuştur ve çoğu Streamterminal işlemi yalnızca veri azaltma işlemidir. BinaryOperator<T>Birçoğunda iyi bir örnek kullanılır Collectors. Bir birinci eleman, ikincisi ile küçültülür, daha sonra bir sonrakinde küçültülebilir, vb. Tabii ki, burada bir Function<T, Function<T, T>func = x -> (y -> / * indirim kodu * /) oluşturabilirsiniz. Ama ciddice? Tüm bunları yapabildiğiniz zaman BinaryOperator<T> func = (x, y) -> /*reduction code here*/. Ayrıca, bu veri azaltma yaklaşımı bana "yıkıcı" yaklaşımınıza çok benziyor.
FBB

32
Bu nasıl bu kadar çok olumlu oy aldı? Bu korkunç ve kafa karıştırıcı bir cevap, çünkü Function<Integer,Integer> f = (x,y) -> x + ygeçerli Java önermesine dayanıyor, öyle değil. Başlamak için bir BiFunction olmalı!
wvdz

162

TriFunction'a ihtiyacınız varsa, şunu yapın:

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

Aşağıdaki küçük program nasıl kullanılabileceğini gösterir. Sonuç türünün, son genel tür parametresi olarak belirtildiğini unutmayın.

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

Sanırım içinde TriFunction için pratik kullanım olsaydı java.util.*veya java.lang.*tanımlanmış olurdu. Ama asla 22 argümanın ötesine geçmem ;-) Bununla demek istediğim, koleksiyonların akışına izin veren tüm yeni kodlar hiçbir zaman yöntem parametresi olarak TriFunction gerektirmez. Yani dahil edilmedi.

GÜNCELLEME

Eksiksizlik ve başka bir cevapta (körlemeyle ilgili) yıkıcı fonksiyon açıklamasını takip etmek için, TriFunction'ın ek arayüz olmadan nasıl taklit edilebileceği aşağıda açıklanmıştır:

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

Elbette, işlevleri başka şekillerde birleştirmek mümkündür, örneğin:

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

Körleme, lambdas'ın ötesinde işlevsel programlamayı destekleyen herhangi bir dil için doğal olsa da, Java bu şekilde inşa edilmemiştir ve ulaşılabilir olsa da, kodun bakımı zordur ve bazen okunabilir. Bununla birlikte, bir alıştırma olarak çok yararlıdır ve bazen kısmi işlevler kodunuzda haklı bir yere sahiptir.


6
Çözüm için teşekkürler. Ve evet, kesinlikle BiFunction, TriFunction için kullanım var ... Aksi takdirde insanlar onu aramayacaktır. Tüm lambda olayının şu anda Oracle için çok yeni olduğunu ve daha sonraki Java sürümlerinde genişletileceğini tahmin ediyor. Şu anda daha çok bir kavram kanıtı.
Stefan Endrullis

Hy @Alex aşağıdaki satırı tanımlayabilir misiniz? burada neler oluyor default <V> TriFunction <A, B, C, V> veThen (Function <? super R,? Extends V> after) {Objects.requireNonNull (after); return (A a, B b, C c) -> after.apply (uygula (a, b, c)); }
Muneeb Nasir

@MuneebNasir - işlev kompozisyonu yapmanızı sağlar: Stackoverflow.com/questions/19834611/…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 konusuna bakın
Alex Pakka

Cevaba andThen()kullanım örneği eklendi .
Alex Pakka

Sadece currying Java diline iyi bir şekilde adapte edilmemiştir, aynı zamanda, yanılıyorsam düzeltin, ancak API'de veri azaltma gerçekleştirmek BiFunctioniçin kullanılır Stream, ki bu bana çok benzeyen bir yaklaşımdır: asla ikiden fazla almazsınız argümanlar ve herhangi bir sayıda öğeyi işleyebilirsiniz, her seferinde bir azaltma (kabul edilen cevap hakkındaki yorumuma bakın, bu şekilde görmekte yanlış olup olmadığımı bilmek beni mutlu eder).
FBB

13

Alternatif, aşağıdaki bağımlılığı ekleyin,

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

Şimdi, Vavr Fonksiyonunu, 8'e kadar argüman gibi,

3 argüman:

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

5 argüman:

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

2
Vavr'dan bahsetmek için cevabımı güncellemek üzereydim, ama sen ilk oldun, ben de oy verdim. Bir TriFunction'a ihtiyacınız olan noktaya gelirseniz, vavrkitaplığı kullanarak daha iyi durumda olma şansınız yüksektir - Java'da mümkün olduğunca işlevsel stil programlamayı katlanılabilir hale getirir.
Alex Pakka

7

Neredeyse aynı soruyu ve kısmi bir cevabım var. Yapıcı / yapısökümcü cevabın dil tasarımcılarının aklında olan şey olup olmadığından emin değilim. Bence 3 veya daha fazla N'ye sahip olmak geçerli kullanım durumlarına sahip.

.NET'ten geliyorum. ve .NET'te void işlevleri için Func ve Action'a sahipsiniz. Dayanak ve diğer bazı özel durumlar da mevcuttur. Bakınız: https://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx

Merak ediyorum, dil tasarımcılarının Function, B Function'ı seçmesinin ve DecaExiFunction'a kadar devam etmemesinin nedeni neydi?

İkinci bölümün cevabı tip silme. Derlemeden sonra Func ve Func arasında fark yoktur. Bu nedenle aşağıdakiler derlenmez:

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

İç işlevler, başka bir küçük sorunu aşmak için kullanıldı. Eclipse, aynı dizinde Function adlı dosyalarda her iki sınıfın da bulunması konusunda ısrar etti ... Bunun günümüzde bir derleyici sorunu olup olmadığından emin değilim. Ama Eclipse'deki hatasını çeviremiyorum.

Func, java İşlev türü ile ad çatışmalarını önlemek için kullanıldı.

Dolayısıyla, 3 ila 16 argümandan Func eklemek isterseniz iki şey yapabilirsiniz.

  • TriFunc, TesseraFunc, PendeFunc, ... DecaExiFunc vb yapın
    • (Yunanca mı yoksa Latince mi kullanmalıyım?)
  • Adları farklı kılmak için paket adlarını veya sınıfları kullanın.

İkinci yol için örnek:

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

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

ve

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

En iyi yaklaşım ne olurdu?

Yukarıdaki örneklerde andThen () ve compose () yöntemleri için uygulamaları dahil etmedim. Bunları eklerseniz, her birine 16 aşırı yükleme eklemeniz gerekir: TriFunc'un 16 bağımsız değişkenli bir and sonra () olması gerekir. Bu, döngüsel bağımlılıklar nedeniyle size bir derleme hatası verir. Ayrıca Function ve BiFunction için bu aşırı yüklere sahip olmazsınız. Bu nedenle, Func'ı bir bağımsız değişkenle ve Func'u iki bağımsız değişkenle tanımlamalısınız. .NET'te döngüsel bağımlılıklar, Java'da bulunmayan uzantı yöntemleri kullanılarak engellenebilir.


2
Neden andThen16 argümana ihtiyacınız var? Java'daki bir işlevin sonucu tek bir değerdir. andThenbu değeri alır ve onunla bir şeyler yapar. Ayrıca adlandırma ile ilgili herhangi bir sorun yoktur. İşlev ve BiFunction ile Java dil geliştiricileri tarafından belirlenen mantığı takip ederek, sınıf adları farklı olmalı ve aynı şekilde adlandırılan farklı dosyalarda bulunmalıdır. Ayrıca, bağımsız değişken türleri farklıysa tüm bu farklı adlara ihtiyaç vardır. VargFunction(T, R) { R apply(T.. t) ... }Tek tip için bir yaratılabilir .
Alex Pakka

2

BiFunction için kaynak kodunu burada buldum:

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

TriFunction oluşturmak için onu değiştirdim. BiFunction gibi, andThen () kullanır ve compose () kullanmaz, bu nedenle compose () gerektiren bazı uygulamalar için uygun olmayabilir. Normal türdeki nesneler için iyi olmalıdır. AndThen () ve compose () hakkında iyi bir makale burada bulunabilir:

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 parametreyi alarak kendi işlevinizi de oluşturabilirsiniz.

@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

1

TriFunction'da her zaman duramazsınız. Bazen, işlevlerinize n sayıda parametre iletmeniz gerekebilir. Ardından destek ekibinin kodunuzu düzeltmek için bir QuadFunction oluşturması gerekecektir. Uzun vadeli çözüm, ekstra parametrelerle bir Nesne oluşturmak ve ardından hazır Fonksiyonu veya BiFunction'ı kullanmak olacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.