Подтвердить что ты не робот

Java 8: Где TriFunction (и род) в java.util.function? Или что альтернатива?

Я вижу 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, я просто пытаюсь понять обоснование, не включающее его в стандартную библиотеку.

4b9b3361

Ответ 1

Насколько я знаю, существует только два вида функций: деструктивный и конструктивный.

В то время как конструктивная функция, как следует из названия, создает что-то, разрушительное разрушает что-то, но не так, как вы можете думать сейчас.

Например, функция

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 конструктивные функции следует обрабатывать (большую часть времени) с помощью ссылок на методы, потому что не так много преимуществ использования конструктивной лямбда-функции. Они немного похожи на статические методы. Вы можете использовать их, но у них нет реального состояния.

Другим типом является деструктивный, он требует чего-то и демонтирует его, насколько это необходимо. Например, деструктивная функция

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 := 1, h(2) для y := 2 и h(3) для y := 3.

Итак, у вас есть фиксированное состояние! Это довольно динамично и в большинстве случаев то, что мы хотим от лямбды.

Шаблоны вроде Factory намного проще, если вы можете просто добавить функцию, которая выполняет вашу работу.

Деструктивные легко объединяются. Если тип прав, вы можете просто составить их по своему усмотрению. Используя это, вы можете легко определить морфизмы, которые делают (с неизменяемыми значениями) тестирование намного проще!

Вы можете сделать это тоже с конструктивной, но деструктивная композиция выглядит лучше и больше похожа на список или декоратор, а конструктивный выглядит очень похоже на дерево. И такие вещи, как откат с конструктивными функциями просто не приятно. Вы можете просто сохранить частичные функции деструктивного (динамическое программирование), а на "backtrack" просто использовать старую деструктивную функцию. Это делает код намного меньшим и лучше читаемым. С конструктивными функциями вы более или менее помните все аргументы, которых может быть много.

Итак, почему существует потребность в BiFunction, которая должна быть более проблематичной, чем то, почему нет TriFunction?

Во-первых, много времени у вас есть только несколько значений (меньше 3), и вам нужен только результат, поэтому нормальная деструктивная функция вообще не понадобится, конструктивный будет хорошо. И есть такие вещи, как монады, которые действительно нужна конструктивная функция. Но помимо этого, на самом деле нет веских причин, по которым существует BiFunction вообще. Это не значит, что его нужно удалить! Я сражаюсь за свои монады, пока не умру!

Итак, если у вас много аргументов, которые вы не можете объединить в класс логических контейнеров, и если вам нужно функция должна быть конструктивной, используйте ссылку на метод. В противном случае попытайтесь использовать новую приобретенную способность деструктивных функций, вы можете обнаружить, что делаете много вещей с гораздо меньшим количеством строк кода.

Ответ 2

Если вам нужна 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

В то время как currying был бы естественным для любого языка, поддерживающего функциональное программирование за пределами lambdas, Java не строится таким образом и, хотя и достижимо, код трудно поддерживать, а иногда и читать. Тем не менее, это очень полезно в качестве упражнения, а иногда частичные функции имеют законное место в вашем коде.

Ответ 3

У меня почти такой же вопрос и частичный ответ. Не уверен, был ли конструктивный/деконструктивный ответ тем, о чем говорили разработчики языка. Я думаю, что у 3 и более до N есть допустимые варианты использования.

Я пришел из .NET. и в .NET у вас есть функции Func и Action для void. Также существуют предикат и некоторые другие особые случаи. См.: https://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx

Интересно, в чем причина, почему дизайнеры языка выбрали функцию Function, B Функционал и не продолжался до DecaExiFunction?

Ответ на вторую часть - стирание типа. После компиляции нет разницы между 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.

Итак, если вы хотите добавить Func из 3 до 16 аргументов, вы можете сделать две вещи.

  • Сделайте TriFunc, TesseraFunc, PendeFunc,... DecaExiFunc и т.д.
    • (Должен ли я использовать греческий или латинский?)
  • Используйте имена пакетов или классы, чтобы сделать имена разными.

Пример для второго способа:

 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 должен иметь andthen() с 16 аргументами. Это даст вам ошибку компиляции из-за круговых зависимостей. Также у вас не было бы этих перегрузок для функции и BiFunction. Поэтому вы должны также определить Func с одним аргументом и Func с двумя аргументами. В круговых зависимостях .NET обойти можно с помощью методов расширения, которых нет в Java.

Ответ 4

Альтернативой является добавление приведенной ниже зависимости,

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

Теперь вы можете использовать функцию Vavr, как показано ниже, до 8 аргументов,

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;

Ответ 5

Я нашел исходный код для BiFunction здесь:

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

Я изменил его, чтобы создать TriFunction. Как и BiFunction, он использует andThen(), а не compose(), поэтому для некоторых приложений, требующих 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));
    }
}

Ответ 6

Вы также можете создать свою собственную функцию, принимая 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