phantasmicmeans 기술 블로그

11. Kotlin - Higher Order Functions 본문

Programming/Kotlin

11. Kotlin - Higher Order Functions

phantasmicmeans 2021. 1. 22. 12:58

Higher-Order Functions

코틀린의 함수는 1급 객체이다. 즉 인자로 전달 될 수 있고, 다른 함수의 리턴값으로도 사용될 수 있다. 또한 변수에 부여될 수 있다.

아래 2가지 조건 중 하나를 만족하는 함수를 Higher-Order-Function(고차원 함수)라 부른다.

  • 함수를 인자로 받는 함수

  • 리턴값으로 함수를 사용하는 함수

자바의 경우 Collections.sort()를 예로 들 수 있다.

첫 번째 인자로 list, 두번째 인자로 Comparator를 받는데 이는 Functional Interface이다.

public static <T> void sort(List<T> list, Comparator<? super T> c)

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    ...
}

자바는 Functional Interface를 통해 람다식에 접근할 수 있고, 람다식은 결국 익명함수를 만드는 목적이므로 Comparator 또한 일단 함수라 생각하고 아래 예를 보면 될 것 같다.

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

Collections.sort(list, (a, b) -> a.compareTo(b))

직접 예를 만들면 아래와 같을 수 있다.

public Integer test(BiFunction<Integer, Integer, Integer> biFunction, Integer a, Integer b) {
    return biFunction.apply(a, b);
}

test((a, b) -> a + b, 1, 2);

위 예시들은 자바의 Functional Interface 중 몇가지의 경우에 불과하기에, 때에 따라서는 Function, Supplier, TriFunction 등 굉장히 다양한 Functional Interface를 활용해야한다.

코틀린과 비교하면 큰 단점이라고 볼 수 있을 것 같다. 코틀린도 자바처럼 Higher-Order-Function을 지원하지만 Funcional Interface에 국한되지 않는다.

단순히 아래처럼 람다식을 바로 사용해서 Higher-Order-Function들을 정의할 수 있다. 아래 함수들은 모두 조건을 만족한다.

    fun simpleHigherOrderFunction(sum: (Int, Int) -> Int, a: Int, b: Int): Int = sum(a, b) 
    val sumInts: (a:Int, b:Int) -> Int = { a, b -> a + b }

    var t0: (a: Int, b: Int, c:Int) -> Int = { a, b, c -> a + b + c}
    var t1 = { a:Int, b:Int, c:Int -> a + b + c }
    var t2 = { a:Int, b:Int, c:Int, d:Int -> a + b + c * d }
    var t3 = { a:Int, b:Int, c:Int, d:Int -> a + b + c - d }

sumInts의 경우 Function Type을 가지고 있는 것인데, Int 타입의 인자 2개를 받아 Int 타입을 리턴하겠다는 명시적 표기법이다.
파라미터가 없는 경우 () -> A 처럼 생략 가능하지만, 리턴타입이 Unit인 경우는 생략이 불가능하다.

Function Type

코틀린은 함수를 다룰 때 타입 선언에 친숙하고 이를 Function Type이라 부른다.

해석은 단순하게 보면 된다. a, b 두 인자를 Int 타입으로 받고, Int 타입의 무언가를 리턴하겠다는 뜻이다.

(a:Int, b:Int) -> Int

예를 들면 아래처럼 사용할 수 있다.

val onClick: () -> Unit = ...

추가로 Function Type은 수신 객체 타입을 가질 수 있다.

아래는 수신 객체인 A가, B 타입의 인자를 받으며 C 타입의 리턴값을 내보내겠다는 의미이고, 이 함수의 호출은 A라는 객체에서 행해지게 된다.
extension function과 동일하게 보일 수 있으나 조금 다르다. 추후에 설명할 내용이니 일단은 다르다는 것만 알아두고 넘어가도 좋다.

A.(B) -> C
  • A: receiver
  • () -> String: FunctionType

이러한 방법은 Function literals with receiver 와 함께 사용된다. 아래를 보자

Function Literals with receiver

kotlin에서 Function literal은 둘 중 하나이면 된다.

  • Lambda expression
  • Anonymous function

어쨌든 function literal을 사용하며 receiver를 가지고 싶을 때, Function Type을 사용한다.

function literal을 들여다보면 수신 객체가 this로 변하게 되어 지정자 없이 사용할 수 있고, 원한다면 this를 붙일 수 있다.

무슨 말인지 이해가 안된다면 아래 예제를 보자.

좌항을 보면 Int 타입의 수신 객체가 Int 타입의 인자를 받고, Int 타입의 리턴 값을 나타내는 Function Type을 가지고 있다.

literal로 쓰인 우측의 람다식은 other -> plus(other) 와 같은 형태로 이루어져 있다. 바로 위에서 함수의 호출은 수신 객체에서 이루어진다고 하였으니 아래 식에서 plus(other) 호출자는 수신 객체가 된다.

val sumOther: Int.(Int) -> Int = { other -> plus(other) }

/* 1이 수신 객체, 2가 인자 / 수신 객체가 this 이므로 plus 함수는 수신 객체가 호출하게 됨 */ 
val sumOtherResult = 1.sumIt(2) 

function literal은 수신 객체가 this로 변하게 되어 지정자 없이 사용할 수 있다고 했으니 이를 다시쓰면 아래처럼 된다.

위와 동일한 코드이다.

val sumThis: Int.(Int) -> Int = { other -> this.plus(other) }
val sumThisResult = 1.sumThis(2)

It keyword와 함께 사용하려면 아래처럼 작성할 수 있다. (이번에도 plus 호출은 당연히 수신객체)

val sumIt: Int.(Int) -> Int = { plus(it) }
val sumItResult = 1.sumIt(2)

this, it를 함께 쓰고싶으면 아래처럼 구성할 수 있다.

val test : Int.(Int) -> Int = { if (this > 0) this * it else this - it }
val testResult = 10.test(3)

추가로, labmda 식을 변수에 대입하지 않고 function 그대로 사용할 수도 있다.

fun doSomethingOnTwo(f: Int.(Int) -> Int) = f(2, 2) // 순서대로 (receiver, parameter)

val multiple = doSomethingOnTwo { this * it }
val plus = doSomethingOnTwo { this + it }
val minus = doSomethingOnTwo { this - it }
val divide = doSomethingOnTwo { this % it }

println("$multiple $plus $minus $divide")

spring webflux의 router 또한 이런 방식으로 이루어져 있으니 참고 정도 해두면 될 것 같다.

fun router(routes: RouterFunctionDsl.() -> Unit) = RouterFunctionDsl(routes).build()

@Bean
fun route() = router { // lambda 
    /** GET 또한 고차원 함수이다 
    * fun GET(pattern: String, f: (ServerRequest) -> Mono<out ServerResponse>)
    **/
    GET("/route") { 
    ServerResponse.ok().body(fromValue(arrayOf(1,2,3)))
    }
}

TypeAlias

FunctionType을 매번 정의하기 귀찮으니 typealias를 통해 미리 정의해놓을 수 있다.

typealias ClickHandler = (Button, ClickEvent) -> Unit

'Programming > Kotlin' 카테고리의 다른 글

12. Kotlin - Extension Function VS Function literal with receiver  (0) 2021.01.22
10. Kotlin - Functional Interface  (0) 2021.01.22
9. Kotlin - Interface  (0) 2021.01.22
8. Kotlin - Backing Field  (0) 2021.01.22
7. Kotlin - Properties  (0) 2021.01.22
Comments