본문 바로가기
Development/Kotlin

코틀린(Kotlin) 문법 뽀개기 - 5. 람다(Lambda), 인라인 함수(Inline Function)

by du.it.ddu 2020. 8. 3.
반응형

코틀린의 람다와 인라인 함수에 대해 알아보자.

코틀린의 공식 문서에서 람다 항목으로 이동하면 고차함수(High-Order Functions)에 대한 설명과 함께 시작하는 것을 볼 수 있다. (https://kotlinlang.org/docs/reference/lambdas.html 참고)

 

Higher-Order Functions and Lambdas - Kotlin Programming Language

 

kotlinlang.org

당연히 관련이 있으니까 언급을 하고 있을 것이다.

고차함수는 함수를 매개변수로 취하거나 함수를 반환하는 함수를 말한다.

그리고 람다 또는 람다 표현식은 이름이 없는 익명 함수이다. 말이 어려우니 코드를 보자.


몇 가지 람다를 작성 해 보겠다.

fun main() {
    val helloWorld: () -> Unit = { println("Hello World") }
    val plus: (Int, Int) -> (Int) = { a: Int, b: Int -> a+b }
    val plusPrint: (Int, Int) -> Unit = { a: Int, b: Int -> println("Plus pinrt :: ${a+b}") }

    helloWorld.invoke()
    println("Plus :: ${plus(1, 2)}")
    plusPrint.invoke(1, 2)
}

helloWorld를 보자. 대괄호 안에 println 함수를 호출한다. 그리고 아래에 호출할 때 invoke()를 호출하였다.

plus를 보자. 대괄호 안에 a, b 매개변수가 존재하고 이를 더하고 있다. 그리고 아래에서 println 함수를 통해 값으로 사용된다.

plusPrint를 보자. plus와 비슷한 형태지만 println을 호출하고 있다. 아래에서 helloWorld와 마찬가지로 invoke를 호출하며 파라미터를 전달하고 있다.

실행하면 위와 같은 결과를 얻을 수 있을 것이다.

마치 변수를 함수처럼 사용 가능하게 하는 것을 볼 수 있다.

이제 람다가 뭐하는 녀석인지, 어떻게 생겼고 어떻게 사용하는지 대충은 알 수 있을 것이다.


람다는 유용한 점이 많다. 안드로이드 개발을 해 보았다면 아래와 같은 차이점을 설명할 수 있겠다.

// Java
someView.setOnClickListener(new onClickListener() {
    @Override
    public void onClick(View view) {
        ...
    }
});

// Kotlin
someView.setOnClickListener { 
    ...
}

코드가 굉장히 간결해졌다. 읽기도 쉽다. Java에서 onClick 함수의 view 파라미터는 Kotlin에선 생략되었다.

이것만 보면 그냥 코드를 짧게 쓸 수 있다는 점인가? 라는 생각만 할 수 있다.

맨 위에서 고차함수에 대해 얘기 했을 것이다.

그럼 고차함수를 작성 해 보자.

fun highOrderFunction(param: Int, lambda: (Int) -> Unit) {
    println("Something.. $param")
    lambda.invoke(param)
}

fun main() {
//    highOrderFunction(1, {
//        println("Handle Lambda Result :: $it")
//    })

    highOrderFunction(1) {
        println("Handle Lambda Result :: $it")
    }
}

highOrderFunction은 Int형 파라미터 하나와 람다를 파라미터로 받고 있다.

main함수의 주석처리 된 부분을 보자. 첫번째 파라미터를 넘기고 뒤에 바로 대괄호를 열어 람다를 작성했다. 

람다함수의 Int 파라미터는 생략되어 it 으로 접근 가능하다.

그리고 위 식은 main 함수의 아래처럼 생략 가능하다.

이러한 방법으로 람다를 파라미터로 받는 함수를 작성해서 콜백 등의 처리를 할 수 있다.

Java였다면? 인터페이스 만들고 오버라이드 하고 코드가 굉장히 길어졌을 것이다. 이제 조금은 람다의 유용함을 알게 되었을까?


이제 Inline Function에 대해 알아보자.

코틀린의 공식 문서에는 다음과 같이 설명한다.

"Using higher-order functions imposes certain runtime penalties: each function is an object, and it captures a closure, i.e. those variables that are accessed in the body of the function. Memory allocations (both for function objects and classes) and virtual calls introduce runtime overhead."

간단하게 말하면, 람다를 사용하면 함수를 객체화 하는 과정에서 런타임 오버헤드를 초래한다는 것이다. 

그래서 이러한 오버헤드를 줄일 수 있는 방법으로 Inline Function을 제공하며, Inline Function으로 정의된 함수는 객체로 만들어지지 않고 코드 자체가 복사되게 된다.


위에서 사용했던 코드를 수정해보자.

inline fun highOrderFunction(param: Int, lambda: (Int) -> Unit) {
    println("Something.. $param")
    lambda.invoke(param)
}

fun main() {
//    highOrderFunction(1, {
//        println("Handle Lambda Result :: $it")
//    })

    highOrderFunction(1) {
        println("Handle Lambda Result :: $it")
    }
}

끝이다. inline 키워드를 붙이면 된다.

실행 결과로 보나 겉으로 보나 큰 차이는 못느낄 수 있다.

하지만 컴파일 후 바이트 코드를 보면 차이가 나지만.. 이건 귀찮으니 생략한다.

하나 추가로, 여러 개의 람다를 받는 함수가 있을 때 인라인으로 하기 싫을 때도 있지 않을까?

그래서 아래와 같이 noinline 키워드로 작성할 수 있다.

inline fun highOrderFunction2(param: Int, lambda1: (Int) -> Unit, noinline lambda2: (Int) -> Unit) {
    // Something
}

쉽다.

자 이렇게 설명하면, 그럼 람다는 다 inline으로 처리하면 오버헤드 없이 사용할 수 있는거 아냐? 라고 생각할 수 있다.

세상에 단점이 없는 것이 어디 있겠는가?

코드를 통째로 복사하니 실제 컴파일 된 소스코드는 길이가 길어진다. 만약 사용되는 곳이 많다면 코드는 더더욱 길어지고 사이즈가 커지게 된다.

그 외에 자기 자신을 재귀적으로 호출할 수 없고 다른 인라인 함수로 부터 불릴 수도 없고 등등이 있다.

접근제어자에 관련한 제약도 존재한다.

결론은 적재적소에 써야 한다는 것인데.. 이건 굉장히 어렵구나..


람다와 인라인 함수에 대한 기본적인 내용을 알기엔 충분하다고 생각하지만.. 좀 더 깊은 내용은 모르는 부분이 많은 것 같다. 공부할 것이 산더미다.

람다는 앞으로 Collection이나 범위지정 함수 등에서 자주 등장하니 잘 알아둘 필요가 있다.

 

반응형