고차함수(High-Order Function)

매개변수로 함수를 전달받거나 함수를 반환하는 함수를 말합니다.

고차 함수 내에서 매개변수로 함수 타입을 선언하고 고차 함수를 호출하는 곳에서 람다 함수를 전달하는 구조 입니다.

아래는 Int 타입 매개변수 두 개를 전달받아 결과로 Int 타입의 데이터를 반환하는 일반함수와

매개변수로 함수를 받는 예시 입니다.

ex)

fun normalFun(a: Int, b: Int): Int = a + b


//매개변수 b는 Int형 매개변수 하나와 반환값 타입이 Int형인 함수를 의미합니다.
fun highFun(a: Int, b: (Int) -> Int) {
    val result = b(10)
    println("a = $a, b = $result")
}

// ....


highFun(10, { x -> x * x})

고차 함수와 함수 타입 매개변수

함수 타입의 매개변수 대입

  • 고차 함수의 마지막 매개변수가 함수 타입이면 함수를 호출할 때 ()를 생략할 수 있습니다.

ex)

fun hoFun(argFun: (Int) -> Int) {
    // ...
}

fun hoFun2(a: Int, argFun1: (Int) -> Int, argFun: (Int) -> Boolean) {
    // ...
}


hoFun({ x -> x * x})
hoFun { x ->
    x * x
}

hoFun2(10, { it + it}, { it > 10})

hoFun2(10, { it + it}) {
    it > 10
}

함수 타입 기본값 이용

  • 일반적인 함수의 매개변수에 기본값을 지정하듯 고차함수에서도 기본값을 선언할 수 있습니다.

ex)

fun hoFun(a: Int, b: (Int) -> Int = {x -> x * x }) {
    // ...
}

hoFun(a)    // 기본값 이용
hoFun(a) {
    it * it
}

고차 함수와 함수 반환

  • 고차 함수에서 함수를 반환하는 방법은 반환 타입에 함수 타입을 선언 하면 됩니다.

ex)

fun hoFun(str: String) : (a: Int, b: Int) -> Int {
    return when (str) {
        "-" -> {a,b -> a-b}
        "*" -> {a,b -> a*b}
        "/" -> {a,b -> a/b}
        else -> {a,b -> a+b}
    }
}

val resultFun = hoFun("*")
resultFun(10,5)

함수 참조와 익명 함수 이용

  • 일반적으로 고차 함수에서 매개변수나 반환값으로 람다 함수를 많이 이용하지만, 함수 참조나 익명 함수를 이용해도 됩니다.

함수 참조를 이용한 함수 전달

  • 함수 참조 연산자 콜론 두 개(::) 를 이용하여 다른 함수를 고차 함수의 인자로 이용하는 방법입니다.
fun hoFun(a: (Int) -> Int) {
    // ...
}

fun someFun(x: Int): Int {
    retunr x * 10
}

// ...

hoFun {
    it * 10
}

hoFun(::someFun)


익명 함수를 이용한 함수 전달

  • 람다식에는 반환을(return) 명시적으로 해줄 수 없기 때문에 주로 반환을 명시적으로 선언하려고 할 때 이용됩니다.

ex)

// 익명 함수는 단어 그대로 이름이 없는 함수를 뜻하며 기존 함수 선언에서 이름이 빠진 형식이다.

val anonyFun = fun(a: Int): Int = a * a
val anonyFun2 = fun(a: Int): Int {
    println("it's anonymous function")
    return a + a
}

fun hoFun(x: (Int) -> Int){
    val result = x(10)
    println("it's High order Function")
}

// ...

hoFun(anonyFun)
hoFun(anonyFun2)

코틀린 API의 유용한 고차함수

run()

inline fun <R> run(block: () -> R): R

inline fun <T, R> T.run(block: T.() -> R): R
  1. 단순히 람다 함수를 실행하고 그 결괏값을 얻는 목적으로 사용합니다.

    • 매개변수에 함수 타입이 있으므로 람다 함수를 전달해 주고 해당 람다 함수의 반환값을 그대로 반환하는 구조
  2. 객체의 멤버에 접근할때 유용하게 사용됩니다.

    • run()를 호출한 객체가 람다 함수에 전달되어 객체의 멤버를 바로 이용할 수 있습니다.

ex)


data class User(
    var name: String,
    var age: Int,
) {
    fun sayName() {
        println("name = $name")
    }
    fun checkAdult(): Boolean = age >= 20
}

val result : Int = run {
    println("it's run call")
    10 + 20     // 마지막 반환 
}

val isAdult : Boolean = User("Lee", 27).run {
    sayName()
    checkAdult()
}

apply()

inline fun <T> T.apply(block: T.() -> Unit): T
  • apply()함수는 apply()함수를 호출한 객체를 반환합니다.

ex)

data class User(
    var name: String,
    var age: Int,
) {
    fun sayName() {
        println("name = $name")
    }
    fun checkAdult(): Boolean = age >= 20
}
//...

val user = User("Lee", 27).apply {
    name = "kim"
    age = 19
    sayName()
    if (checkAdult())
        println("성인입니다.")
    else
        println("성인이 아닙니다.")

}

let()

inline fun <T, R> T.let(block: (T) -> R): R
  • 호출한 객체(T)를 매개변수인 람다 함수(위의 block)에 매개변수(T)로 전달후 해당 람다 함수의 반환값(R)을 그대로 반환(R) 합니다.

  • 변수 선언을 줄이고자 할 때 사용이 용이합니다.

data class User(
    val name: String,
    val age: Int
)

// ...
User("Lee",27).let {
    println(it.name)
    println(it.age)
}

with() 함수

inline fun <T, R> with(receiver: T, block: T.() -> R): R
  • run() 함수와 유사 하며 차이점은 run()함수는 자신을 호출한 객체를 이용하지만, with()함수는 매개변수로 지정한 객체를 이용합니다.
data class User(
    var name: String,
    var age: Int,
) {
    fun sayHello() {
        println("hello")
    }
}

val user = User("lee", 27)

with(user) {
    name = "kim"
    sayHello()
}

// 동일한 동작

uer.run {
    name = "kim"
    sayHello()
}

인라인 함수

인라인 함수란?

  • 고차 함수에 람다 함수를 전달하고 람다 함수를 이용하는 코드가 많아져서 런타임 때 성능 문제가 발생할 수 있기에, 컴파일 단계에서 inline으로 정의한 함수의 내용이 호출되는 곳에 정적으로포함 되므로 런타임 때 함수 호출이 그만큼 줄고 성능에 도움이 됩니다.

자세한 정리가 된 블로그


참조 : 깡샘의 코틀린 프로그래밍