Java 8 : java.util.function에서 TriFunction (및 kin)은 어디에 있습니까? 아니면 대안은 무엇입니까?


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
bifunction 인터페이스를 사용하면 N-function 클래스를 쉽게 정의 할 수 있습니다. trifunction을 별도의 인터페이스로 정의하면 첫 번째 sb는 왜 quadofunction이 아닌지 묻고 두 번째는 Bifunction을 매개 변수로 사용하는 모든 메소드를 복제해야합니다
user902383

6
이와 같은 API에 대한 수익이 감소하는 지점이 있습니다. (개인적으로, 나는 JDK8 다시 동안 그것을 통과 생각하지만,이 경우에도 그 이상입니다.)
루이 Wasserman에게

그 근거는 Function과 BiFunction이 객체와 네이티브 유형으로 완전히 구현되었다고 말하는 것입니다. 모든 다양한 변형이있는 TriFunctions를 포함하면 JRE가 클래스와 메서드로 날아갈 것입니다.
Thorbjørn Ravn Andersen 19

1
짧은 답변. Java에서 보이지 않으면 직접 빌드합니다 (물론 Alex P 답변 참조). 참고, C #에서 dotnet 구현자는 미리 준비된 항목 (최대 16 개 인수)을 제공했지만 접두사 이름 ( "Bi"여기)은 포함하지 않았습니다. docs.microsoft.com/en-us/dotnet/api/…를 참조하십시오 . 단순한 "Func"입니다. 그래서 이것은 내가 자바보다 dotnet을 선호하는 곳 중 하나입니다. 이 댓글 섹션을 끔찍한 전쟁으로 바꾸지 마십시오. 주석은 BiFunction으로 만 제한합니다.
granadaCoder

답변:


81

내가 아는 한 파괴적인 기능과 건설적인 기능은 두 종류뿐입니다.

건설적인 기능은 이름에서 알 수 있듯이 무언가를 생성하지만 파괴적인 기능은 무언가를 파괴하지만 지금 생각하는 방식은 아닙니다.

예를 들어, 함수

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

A는 건설적인 일. 당신이 무언가를 만들어야 할 때. 예제에서 튜플 (x, y)를 구성했습니다 . 구성 함수에는 무한 인수를 처리 할 수 ​​없다는 문제가 있습니다. 그러나 최악의 것은 논쟁을 열어 둘 수 없다는 것입니다. "글쎄, let 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 := 2h(3)위해 y := 3.

여기에 고정 된 상태가 있습니다! 그것은 매우 역동적이며 우리가 람다에서 원하는 대부분의 시간입니다.

Factory와 같은 패턴은 당신을 위해 일하는 함수를 넣을 수 있다면 훨씬 쉽습니다.

파괴적인 것은 서로 쉽게 결합됩니다. 유형이 맞으면 원하는대로 구성 할 수 있습니다. 이를 사용하면 (불변 값으로) 테스트를 훨씬 쉽게 만드는 형태를 쉽게 정의 할 수 있습니다!

건설적인 구성으로도 그렇게 할 수 있지만 파괴적인 구성은 더 멋지고 목록이나 장식 자처럼 보이며 건설적인 구성은 나무처럼 보입니다. 그리고 건설적인 기능을 사용한 역 추적과 같은 것은 좋지 않습니다. 파괴적인 기능 (동적 프로그래밍)의 부분 기능을 저장하고 "역 추적"에서 이전 파괴 기능을 사용하면됩니다. 따라서 코드가 훨씬 작아지고 가독성이 향상됩니다. 건설적인 함수를 사용하면 모든 인수를 기억할 정도가 많거나 적습니다.

그렇다면 왜 BiFunction없는 것보다 더 많은 의문이 있어야 할 필요 가 TriFunction있는가?

우선, 많은 시간 동안 몇 개의 값 (3 미만) 만 있고 결과 만 필요하므로 정상적인 파괴 함수는 전혀 필요하지 않으며 건설적인 함수는 괜찮습니다. 그리고 정말로 건설적인 기능이 필요한 모나드와 같은 것들이 있습니다. 그러나 그 외에는 전혀 존재하는 이유가 많지 않습니다 BiFunction. 그렇다고 제거해야한다는 의미는 아닙니다! 죽을 때까지 모나드를 위해 싸워요!

따라서 논리 컨테이너 클래스로 결합 할 수없는 인수가 많고 함수가 구성 적이어야하는 경우 메서드 참조를 사용합니다. 그렇지 않으면 파괴적인 기능의 새로운 기능을 사용하려고 시도하면 훨씬 적은 코드 라인으로 많은 일을 할 수 있습니다.


2
당신은 내 질문에 대답했다 ... 내 생각에 ... 나는 자바 언어 디자이너가 이런 생각에서 오는지 모르겠지만, 나는 함수형 프로그래밍에 정통하지 않다. 설명 해주셔서 감사합니다.
Richard Finegan 2014

79
나는 당신이 설명하는 개념을 지칭하기 위해 건설 적이고 파괴적인 용어 가 사용되는 것을 본 적이 없습니다 . 카레비 카레 가 더 일반적인 용어 라고 생각 합니다.
Feuermurmel 2015 년

17
첫 번째 함수 예제는 구문 상 올바르지 않습니다. 두 개의 입력 인수를 사용하므로 함수가 아닌 BiFunction이어야합니다.
annouk

3
IMO BiFunction는 손쉬운 데이터 감소를 위해 만들어졌으며 대부분 Stream의 터미널 운영은 데이터 감소에 불과합니다. 좋은 예는 BinaryOperator<T>많은 곳에서 사용됩니다 Collectors. 첫 번째 요소는 두 번째 요소로 축소되고 다음 요소로 축소 될 수 있습니다. 물론 Function<T, Function<T, T>func = x-> (y-> / * 여기에 축소 코드 * /)를 만들 수 있습니다. 하지만 진지하게? 이 모든 것을 간단하게 할 수 있습니다 BinaryOperator<T> func = (x, y) -> /*reduction code here*/. 또한이 데이터 감소 접근 방식은 저에게 "파괴적인"접근 방식과 매우 흡사합니다.
FBB

32
어떻게 이렇게 많은 찬성표를 얻었습니까? 그것은 끔찍하고 혼란스러운 대답입니다. 왜냐하면 그것은 Function<Integer,Integer> f = (x,y) -> x + y유효한 Java 라는 전제를 기반으로하기 때문 입니다. 시작하려면 BiFunction이어야합니다!
wvdz

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 등에 대한 사용이 확실히 있습니다. 그렇지 않으면 사람들이 검색하지 않을 것입니다. 람다 전체가 현재 오라클에게는 너무 새롭고 이후 Java 버전에서 확장 될 것으로 추측됩니다. 현재로서는 개념 증명에 가깝습니다.
Stefan Endrullis 2014

Hy @Alex 다음 줄을 정의 해 주시겠습니까? 여기서 무슨 일이 일어나고 있는지 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)); }
Muneeb 나시

- @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 Pakka 2015 년

커링은 자바 언어에 잘 맞지 않을뿐만 아니라 내가 틀렸다면 수정 해줄뿐만 아니라 API에서 데이터 감소를 수행 BiFunction하는 데 사용 Stream되는데, 이는 나에게 커링 접근 방식과 매우 비슷해 보입니다. 여러 요소를 처리 할 수 ​​있습니다. 한 번에 하나씩 감소합니다 (허용 된 답변에 대한 내 의견을 참조하십시오. 그런 식으로 보는 것이 잘못되었는지 알게되어 기쁩니다).
FBB

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까지 계속하지 않은 이유가 무엇인지 궁금합니다.

두 번째 부분에 대한 대답은 유형 삭제입니다. 컴파일 후 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는 자바 함수 유형과 이름 충돌을 방지하기 위해 사용되었습니다.

따라서 3에서 16 개의 인수까지 Func를 추가하려면 두 가지를 수행 할 수 있습니다.

  • 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에는 16 개의 인수가있는 andthen ()이 있어야합니다. 순환 종속성으로 인해 컴파일 오류가 발생합니다. 또한 Function 및 BiFunction에 대해 이러한 과부하가 발생하지 않습니다. 따라서 하나의 인수로 Func를 정의하고 두 개의 인수로 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을 만들어야합니다. 장기적인 해결책은 추가 매개 변수로 Object를 생성 한 다음 기성품 Function 또는 BiFunction을 사용하는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.