Groo

Kotlin 코틀린의 고참 함수와 람다식 본문

프로그래밍 언어/Kotlin

Kotlin 코틀린의 고참 함수와 람다식

김주엽 2020. 1. 14. 22:56

안녕하세요, 오늘은 Kotlin 코틀린 언어의 고차 함수와 람다식에 대해서 알아보려고 합니다.
이번에 배우는 고차 함수와 람다식은 코틀린 언어의 핵심이라고도 할 수 있으며 많이 어렵기도 합니다.

🙋‍♂️ 일반 함수를 인자나 반환 값으로?

고차 함수는 인자나 반환 값에 함수를 사용해서 대단히 유연하다는 특징을 가지고 있습니다. 고차 함수를 사용하는 시기는 대부분 함수의 인자나, 반환 값으로 고차 함수를 많이 이용하고 있습니다. 그 형태가 이름이 있는 일반적인 함수일 수도 있으며 이름이 없는 람다식 함수 형태일 수도 있습니다. 아래의 일반적인 함수에서 함수의 인자로 함수를 전달하는 고차 함수의 예시를 보도록 하겠습니다.

fun main() {
    val res1 = sum(3,2)
    val res2 = mul(sum(3,3), 3) // mul 함수의 인자로 sum 함수의 반환 값 전달

    println("res1 : $res1, res2 : $res2")
}

fun sum(a : Int, b : Int) = a + b
fun mul(a : Int, b : Int) = a * b

위에서는 일반 함수의 인자로 함수를 전달하는 예시를 보았습니다. 인자로 함수를 전달하면서 그 함수의 반환 값을 다시 전달하는 것이죠. 그럼 이번에는 고참 함수의 두 번째 특징인 함수의 반환 값으로 고차 함수를 전달하는 예시를 확인해 보도록 하겠습니다.

fun main() {
    println("funcFunc : ${funcFunc()}")
}

fun sum(a : Int, b : Int) = a + b
fun funcFunc() = sum(2, 2) // 함수의 반환 값으로 함수를 전달

🙋‍♀️ 람다식을 일반 변수의 값으로?

앞에서는 일반 함수를 함수의 인자나 반환 값으로 사용하는 것을 알아보았습니다. 일반 함수를 사용한 것은 크게 어렵지 않았습니다. 하지만 추후 배우는 이름이 없는 함수 즉 람다식을 인자나 반환 값으로 활용하는 것은 조금 더 어려울 수도 있습니다. 그래서 지금은 먼저 람다식을 적응하기 위해 람다식을 활용해서 일반 변수에 저장하는 예시를 보도록 하겠습니다.

fun main() {
    val result : Int
    val multi : (Int, Int) -> Int = {x : Int, y : Int -> x * y} // 람다식 함수를 변수에 할당

    result = multi(10, 20)
    println("result : $result")
}

위의 코드를 해석해보면 multi 변수에 람다식 함수로 초기화 하는 모습을 볼 수 있습니다. 순서대로 람다식 함수의 자료형과, 반환 값 자료형, 매개변수, 반환 값으로 이루어져 있는 것을 볼 수 있습니다. 하지만 반환 값이 한 줄이 아닌 여러 줄이면 어떻게 될까요?

fun main() {
    val result : Int
    val multi = {x : Int, y : Int -> println("x * y") x * y} // 마지막 코드가 반환 값으로 지정

    result = multi(10, 20)
    println("result : $result")
}

그럼 만약 반환 값과 매개변수가 아예 없거나 매개변수가 한 개만 존재할 경우에는 어떻게 대응을 해야할까요? 만약 반환 값이 없다면 이전에 배운 void와 같은 Kotlin 에서의 Unit 예약어를 사용하면 코틀린만의 반환 값이 없다는 특수한 객체가 전달됩니다.

val greet : () -> Unit = {println("Hello World!")} // 매개변수와 반환 값이 없는 경우
val square : (Int) -> Int = {x : Int -> x * x} // 매개변수가 한 개인 경우

🎨 람다식을 인자나 반환 값으로?

위에서 람다식을 활용하여 간단한 예제를 구성하였다면, 이제는 람다식을 활용하여 함수의 인자나 반환 값으로 사용해보는 고차 함수의 예제를 해보겠습니다. 이 부분 부터는 조금 난이도가 높고 어려울 수도 있으므로 천천히 잘 이해하면서 따라오는 것이 중요합니다.

fun main() {
    var result : Int

    result = highOrder({x, y -> x + y}, 10, 20) // 함수의 인자로 람다식 전달
    println(result)
}

// 함수의 매개변수로 람다식 값을 받을 준비 및 반환 값으로 매개변수에 sum 함수 값 반환
fun highOrder(sum : (Int, Int) -> Int, a : Int, b : Int) = sum(a, b)

위의 코드에 대해 설명하면 result 변수에 highOrder 함수를 저장하고 있으며 첫 번째 인자로 람다식 초기화 부분을 전달하고 있으며 highOrder 함수의 매개변수에서는 인자로 전달될 람다식 값을 받을 수 있도록 람다식의 자료형을 초기화 하고 있습니다.

fun main() {
    val out : () -> Unit = { println("Hello World!")} // out 변수에 람다식 정의
    out()

    val new = out // new 변수에 out 변수 값 할당
    new ()
}

위의 코드는 람다식 함수에 매개변수와 반환 값이 없는 함수의 예를 든 것입니다. out 변수에 람다식 함수를 정의하였으며 함수를 호출할 때는 out() 처럼 인자를 전달하지 않고 기본적으로 호출을 하면됩니다. 또한 새로운 new 변수에 out 변수에 존재하는 값을 초기화 하여 new 변수 또한 out 변수에서 현재 가지고 있는 람다식 함수를 똑같이 사용할 수 있는 것을 볼 수 있습니다.

📚 다양한 호출 방식에 대해서 알아보자!

함수의 내용을 할당하거나 인자 혹은 반환 값을 자유롭게 넘기려면 호출 방법을 이해해야합니다. C언어와 달리 Kotlin에서는 포인터 주소 연산이 없기 때문에 주소 자체를 사용하는 참조에 의한 (Call By Reference)가 아닌 값을 복사하여 전달하는 값에 의한 호출 (Call By Value)가 일반적입니다. 따라서 코틀린은 람다식을 사용하면서 몇 가지 확장된 호출 방법을 사용할 수 있습니다. 대표적으로 (1) 값에 의한 호출, (2) 이름에 의한 람다식 호출, (3) 다른 함수의 참조에 의한 일반 함수 호출 이렇게 3가지로 구성할 수 있습니다.


📕 값에 의한 호출이란?

함수가 또 다른 함수의 매개변수 즉 인자로 전달될 경우 람다식 함수는 함수의 과정에서 값으로 처리 되어 그 즉시 람다식 함수가 수행된 후 그 함수의 반환 값을 전달합니다. 아래의 예시를 통해 값에 의한 호출의 방식에 대해서 알아보겠습니다.

fun main() {
    val result = callByValue(lambda()) // lambda() 람다식 함수 호출
    println(result)
}

fun callByValue(b : Boolean) = b

val lambda : () -> Boolean = {true} // lambda 일반 변수에 람다식 함수 적용

위의 코드에서는 일반적인 변수 result 변수에 callByValue() 함수 안에 lambda() 함수가 적용된 것을 볼 수 있습니다. 이러한 과정 속에서 lambda 람다식 함수가 가장 먼저 출발하여 true 를 반환하며 그 값을 callByValue 함수의 매개변수 즉 인자로 전달을 한 후 그 값을 다시 반환 받아 최종적으로 result 변수에 true 값이 저장되는 것을 볼 수 있습니다.

📒 이름에 의한 람다식 호출이란?

이름에 의한 람다식 호출은 값에 의한 호출과 비슷한 부분이 많습니다. 하지만 람다식의 이름이 인자로 전달될 때 바로 실행을 시키는 것이 아닌 실제로 호출될 시 실행을 시키도록 설정을 하는 것입니다. 즉 변수에 람다식 함수를 설정해둘 시 인자로 전달될 때 실행을 시킬 수도 있으며 자신이 실행시키고 싶은 특정 시간대에 자유롭게 실행을 시킬 수 있는 장점이 있습니다.

fun main() {
    val result = callByName(otherLambda) // otherLambda 변수 람다식 이름으로 호출
    println(result)
}

fun callByName(b : () -> Boolean) = b() // 매개변수 b 즉 otherLambda 람다식 함수 호출

val otherLambda = {true} 

result 변수에 callByName 함수가 포함되어있습니다. 현재 callByName 함수의 인자로 otherLambda 변수를 전달하고 있습니다. 위에서 값에 의한 호출에서는 otherLambda() 이런 식으로 람다식 함수를 호출하여 반환된 값을 바로 전달 하였다면 이름에 의한 람다식 호출은 otherLambda 즉 이름 만을 전달하고 있습니다. callByName 함수에서는 otherLambda 변수를 b 에 저장하고 있으며 람다식 선언 자료형으로 인자를 받고 있으며 그 후 b 즉 otherLambda 함수를 호출하여 이제서야 otherLambda 변수 람다 함수가 호출되어 true 값을 반환하는 것을 볼 수 있습니다. 그 후 result 변수에 true 값이 포함됩니다.

📗 다른 함수의 참조에 의한 일반 함수 호출

앞에서 저희는 람다식을 다른 함수의 인자로 전달하여 다른 함수의 매개변수에서 람다식을 호출하는 것을 해보았습니다. 이번에는 일반 함수를 인자로 전달하여 다른 함수의 매개변수에서 호출하는 것을 해보겠습니다. 아래의 sum 함수는 일반적인 함수입니다.

fun sum(a: Int, b : Int) = a + b
fun main(){
    funcParam(3, 2, ::sum) // 2개의 콜론 기호를 사용하는 모습
}
fun sum(a : Int, b : Int) = a + b

// sum 함수의 매개변수의 수와 자료형이 같다. (한 개라도 다르면 오류가 발생한다.)
fun funcParam(a : Int, b : Int, c : (Int, Int) -> Int) = c(a, b) 

평범한 sum 함수를 아래의 고차 함수인 funcParam 함수에서 호출을 하려고 인자로 사용하고 있습니다. 하지만 sum 함수는 람다식이 아니기 때문에 sum 이름으로 호출을 할 수 없습니다. 하지만 funcParam 함수의 세 번째 매개변수는 sum 함수의 인자 수와 자료형의 개수가 동일합니다. 이럴 때는 다음과 같이 콜론(::) 기호를 함수 이름 앞에 붙여 사용할 수 있습니다.

fun main() {
    val res1 = funcParam(3, 2, ::sum3)
    println(res1)

    hello(::text)

    val likeLambda = ::sum3
    println(likeLambda(6, 6))
}

fun sum3(a : Int, b : Int) = a + b
fun text(a : String, b : String) = "Hi! $a $b"

fun funcParam(a : Int, b : Int, c : (Int, Int) -> Int) = c(a, b)
fun hello(body : (String, String) -> String) = println(body("Hello", "World"))

위의 코드는 일반 함수를 인자로 전달하여 다른 함수의 매개변수에서 호출하는 등 다양하게 활용을 하여 구성한 예제입니다. 이 부분은 이해하기가 많이 까다로울 것입니다. 하지만 천천히 오랫동안 보면서 어떻게 구성이 되고 있는지 확인을 하는 것이 중요합니다.


👍 글을 마치며

오늘은 코틀린 언어의 대표적인 특징 중 한개인 고차함수와 람다식에 대해서 자세히 알아보는 시간을 가졌습니다. 두 가지 모두 코드를 작성할 때 유연성이 높다는 특징이 있습니다. 그러나 처음 접하보다 보면 아직 어색하고 이해가 잘 안되는 부분들이 많습니다. 저 또한 오늘 글을 포스팅하기 위해 고차함수와 람다식 파트를 몇 번씩이나 공부를 하고 책을 참고하면서 이번 글을 적었던 것 같습니다. 고차함수와 람다식 내용을 배운 후 저는 이 두가지의 특징을 잘 활용하여 코드를 작성하면 정말 효율적이고 현대 프로그래밍 트렌드에도 잘 맞을 것일고 생각이 들었습니다. 오늘 배운 내용은 다시 한 번 복습할 것이며 정말 중요하다고 저는 생각을 합니다.

 

참고 : Do it 코틀린 프로그래밍

Comments