티스토리 뷰

반응형
SMALL

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

fun highFunction(a: Int, argFunction: (Int) -> Int) {
    val result = argFunction(10)
    println("a : $a, highFunction : $result")
}

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

(Int) -> Int 타입의 함수를 argFunction이라는 이름으로 사용할 수 있다는 것입니다.

위의 코드에서는 highFunction의 매개변수로 람다 함수를 사용한 예시입니다.

 

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

함수 타입의 매개변수 대입

일반적으로 함수를 호출할 때는 함수명 뒤에 ()를 붙이고 () 안에 인수를 작성합니다.

그런데 고차 함수의 매개 변수가 함수 타입이면 함수 호출 시 ( )를 생략할 수 있습니다.

fun highFunction(argFun:(Int) -> Int) {
    val result = argFun(10)
    println("result : $result")
}

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

 

위와 같은 함수 호출은 컬렉션 타입을 이용할 때 자주 보입니다. 배열을 선언하고 이를 사용할 때 예시가 있습니다.

val array = arrayOf(10, 20, 15, 22, 8)
array.filter { x -> x > 10 }
     .forEach { x -> println(x) }

filter라는 함수는 다음과 같이 선언되어  있습니다.

inline fun IntArray.filter(
    predicate: (Int) -> Boolean
): List<Int>

filter함수의 매개 변수는 (Int) -> Boolean으로 선언되어 있습니다. 고차 함수로 선언이 되어 있습니다.

매개변수가 하나이므로 ( )를 생략할 수 있습니다.

 

함수 타입 기본값 이용

함수를 선언할 때 매개 변수에 기본 값을 지정할 수 있는 것은 알고 계실 것입니다.

고차 함수에서도 이것이 가능합니다. 다시 말해 기본 함수를 선언할 수 있습니다.

fun highFun(
    x1: Int,
    argFun1: (Int) -> Int,
    argFun2: (Int) -> Boolean = { x: Int -> x > 10 }
) {
    val result = argFun1(x1)
    println("result: ${argFun2(result)}")
}

highFun(2, {x: Int -> x * x}, {x: Int -> x > 20})
highFun(2, {x: Int -> x * x})

기본 함수가 정의되어 있기 때문에 고차 함수의 매개변수로 지정하지 않아도 함수가 문제 없이 실행되는 것을 알 수 있습니다.

 

고차함수와 함수 반환

고차 함수는 다른 함수를 매개 변수로 받거나 반환하는 함수입니다.

이번에는 고차 함수에서 함수를 반환하는 방법에 대해 알아보도록 하겠습니다.

fun highFunction(str: String): (x1: Int, x2: Int) -> Int {
    when (str) {
        "-" -> return { x1, x2 -> x1 - x2 }
        "+" -> return { x1, x2 -> x1 + x2 }
        "*" -> return { x1, x2 -> x1 * x2 }
        else -> return { x1, x2 -> x1 / x2 }
    }
}

val resultFunction = highFunction("*")
println("result * : ${resultFunction(10, 5)}")

함수의 반환 타입이 함수 타입 형태인 것을 알 수 있습니다.

when 절 안을 살펴 보면 람다 함수식을 return 하는 것으로 보아 함수를 반환하는 것으로 알 수 있습니다.

 

함수 참조와 익명 함수 이용

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

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

고차 함수를 이용할 때 람다 함수 외에 함수 참조 연산자로 콜론 2개 (::) 를 사용할 수도 있습니다.

fun highFunction(argFun: (x: Int) -> Int) {
    println("${argFun(10)}")
}

fun nameFun(x: Int): Int { 
    return x * 5
}

highFunction(::nameFun)

fun 예약어로 이름이 있는 함수를 선언하고 이 함수를 고차 함수의 매개변수로 지정하려면 :: 를 이용하여 전달할 수 있습니다.

 

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

람다 함수는 이름이 없는 익명 함수입니다. 이름이 없기 때문에 주로 고차 함수의 매개변수, 반환 값으로 사용합니다.

하지만 람다 함수는 return 예약어를 사용하여 반환 값을 명시할 수 없습니다.

val lambdaFun = { x: Int ->
    println("i am lambda Function")
    return x * 10
} // ERROR

위의 코드에서는 에러가 발생합니다.

아래 코드를 보면 익명 함수를 fun이라는 예약어를 사용하여 생성하고 이 함수 안에서 return이라는 키워드를 통해 반환값을 명시한 것을 확인할 수 있습니다.

val anonyFun1 = fun(x: Int): Int = x * 10

val anonyFun2 = fun(x: Int) : Int {
    println("i am anonymous function")
    return x * 10
}

자 이제 익명 함수를 매개 변수로 전달하는 방법을 예제를 통해 살펴봅시다.

fun highFunc(argFunc: (Int) -> Int) {
    println("${argFunc(10)}")
}

highFunc(fun(x: Int): Int = x * 10)

 

코틀린 API의 유용한 고차 함수 (run, apply, let, with)

Kotlin의 API를 보면 많은 함수가 고차 함수로 선언되어 있습니다. 이 중 대표적인 고차 함수 4가지를 살펴보도록 하겠습니다.

run() 함수

단순하게 람다 함수를 실행하고 그 결과값을 얻는 목적으로 사용하는 함수입니다. 혹은 객체의 멤버에 접근하는 용도로 사용됩니다.

람다 함수를 실행하기 위한 목적으로 사용하는 run 함수는 아래처럼 정의되어 있습니다.

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

매개 변수에 함수 타입이 선언되어 있으므로 람다 함수를 통해 전달해주면 됩니다.

val result = run {
    println("lambda function call")
    10 + 20
}

println("result : $result")

 

run() 함수는 객체의 여러 멤버(함수, 변수)에 접근할 때도 유용하게 사용이 가능합니다.

일종의 코드를 축약하려는 목적이라고 보시면 될 것 같습니다. 이런 목적의 run 함수는 아래처럼 정의됩니다.

inline fun <T, R> T.run(block: T.() -> R): R

run()함수를 호출한 객체가 람다 함수에 전달되어 람다 함수 내에서는 따로 객체를 명시하지 않고 객체의 멤버를 바로 이용가능합니다.

class User() {
    var name: String? = null
    var age: Int? = null
    
    fun sayHello() {
        println("Hello $name")
    }
    
    fun sayInfo() {
        println("i am $name, $age years old")
    }
}

val user = User()
user.name = "KIM"
user.age = 25
user.sayHello()
user.sayInfo()

위 코드를 보면 User 라는 클래스가 있고 클래스 내에 변수와 함수를 선언했습니다.

그런데 접근하려는 멤버가 많으면 user.name, user.age와 같은 코드가 여러 번 사용되어야 할 것입니다.

이 때 run()함수를 사용하면 조금 더 쉽게 작성할 수 있습니다.

val runResult = user.run{
    name = "KIM"
    age = 25
    sayHello()
    sayInfo()
    10 + 20
}

println("run result : $runResult")

이렇게 하면 출력 결과는 다음과 같습니다.

hello KIM
i am KIM, 25 years old
run result : 30

user.name 과 같은 형태로 접근했던 것을 name으로 바로 접근이 가능하게 되었습니다.

 

apply() 함수

이 함수는 run()와 사용목적은 같습니다. 하지만 반환하는 값에 차이가 있습니다.

run() 함수는 전달받은 람다 함수의 반환 값을 그대로 반환하지만 apply()함수는 apply()함수를 적용한 객체를 반환합니다.

inline fun <T> T.apply(block: T.() -> Unit): T

 

T.apply(~~~~): T로 선언되었으므로 전달받은 람다 함수의 반환 값이 아닌 apply 함수를 호출한 객체가 반환됩니다.

val user2 = user.apply {
    name = "Kim"
    sayHello()
    sayInfo()
}

println("user name: ${user.name}, user2 name : ${user2.name}")
user.name = "aaa"
user2.name = "bbb"
println("user name : ${user.name}, user2 name : ${user2.name}")

user는 User 객체라고 생각하고 살펴보도록 합시다.

user2는 user.apply의 반환 값입니다. 그런데 user2.name으로 멤버에 접근하는 것을 알 수 있습니다.

apply 함수는 함수를 호출한 객체 자신을 반환합니다. 출력 결과는 아래와 같습니다.

hello Kim
i am Kim, 25 years old
user name : Kim, user2 name : Kim
user name : bbb, user2 name : bbb

조금 이상한 것을 알수 있는 부분이 분명 user.name에 aaa을 넣었는데 bbb가 나왔다는 것입니다.

apply 함수를 호출한 객체와 여기서 반환된 객체가 동일 객체라는 것을 알 수 있습니다.

즉, user 객체와 user3 객체는 동일한 객체이지만 이름만 다른 것이라고 알아 두시면 될 것 같습니다.

 

let() 함수

이 함수는 자신을 호출한 객체를 매개변수로 전달받은 람다 함수에 매개변수로 전달하는 함수입니다.

그리고 람다 함수의 반환 값을 let() 함수의 반환값으로 그대로 전달해 줍니다.

class User() {
    var name: String? = null
    var age: Int? = null
    
    constructor(name: String, age: Int): this() {
        this.name = name
        this.age = age
    }
}

fun letTestFun(user: User) {
    println("letTestFun() : ${user.name} / ${user.age}")
}

val user2 = User("KIM", 25)
letTestFun(user2)

letTestFun 이라는 함수는 User타입의 객체를 매개변수로 전달해야하는 함수입니다. 

그래서 User라는 타입의 객체를 생성하고 이를 함수의 인자로 전달하였습니다. 그런데 이런 과정을 let을 사용할 수 있습니다.

위 코드에서 val user2 ~ letTestFun(user2) 부분을 아래 처럼 변경할 수 있습니다.

User("KIM", 25).let { user ->
    letTestFun(user)
}

여기서 user와 letTestFun의 인자를 같으므로 it을 사용하여 더 간단하게 나타낼 수 있습니다.

User("KIM", 25).let {
    letTestFun(it)
}

 

with() 함수

이 함수는 run()과 사용 목적이 비슷한데 객체 멤버에 반복적으로 접근하고 싶을 때 객체 명을 명시하지 않고 접근하려는 용도입니다.

run과의 차이가 있다면 run은 자기를 호출한 객체를 이용하지만 with는 매개 변수로 전달한 객체를 이용합니다.

user.run {
    name = "KIM"
    sayHello()
}

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

함수를 사용한 객체를 { } 내에서 이용하느냐 함수의 매개변수로 지정된 객체를 { } 내에서 이용하느냐의 차이입니다.

 

이번 포스트에서는 고차 함수에 대해서 알아보았습니다.

다음 포스트에서는 위에서 고차함수의 함수 정의 부분에 자주 등장했던 인라인 함수에 대해 알아보도록 하겠습니다.

반응형
LIST
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함