범위 함수 (Scope Function)

apply / run / with / let / also

범위 함수 (Scope Function)

수신 객체 지정 람다 (람다 리시버, Lambda Receiver)


코드를 작성하다 보면 사용하는 객체를 반복해서 명시하는 경우가 많은데, 수신 객체 지정 람다는 이런 수신 객체(Lambda Object)를 명시하지 않고 람다의 본문 안에서 다른 객체의 메소드를 호출할 수 있게 하는 것을 말한다.

이것이 가능한 이유는 블록(block) 람다식에서 수신 객체를 람다의 수신 객체 또는 람다의 입력 파라미터로 사용하였기 때문이다.


범위 함수 종류


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

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

// with
inline fun <T, R> with(receiver: T, block: T.() -> R): R

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

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


// 제네릭 약어
// E - Element
// K - Key
// N - Number
// T - Type
// V - Value
// R - Return Type



apply


수신 객체의 내부 프로퍼티를 설정한 후 수신 객체 자신을 반환하는 함수이다.

  • 람다식의 수신 객체가 apply 의 수신 객체여서 수신 객체에 대한 명시(this)를 생략하는 것이 가능하다.
  • 객체 생성 시에 다양한 프로퍼티를 설정해야 하는 경우에 사용한다.
inline fun <T> T.apply(block: T.() -> Unit): T
// T.apply 의 T 는 apply 의 수신 객체
// block: T.() 의 T 는 람다의 수신 객체
// inline fun <T> T.apply(block: T.() -> Unit): T
fun main() {
    val user = User().apply {
        name = "BBB"
        age = 20
    }
}

data class User(
    var name: String = "AAA", 
    var age: Int = 0
)



run


apply 와 같은 동작을 하지만 블록의 마지막 라인을 반환하는 함수이다.

  • 블록의 마지막 라인이 없다면 Unit 을 반환한다.
  • 수신 객체에 대한 특정한 동작 수행 후 결과값을 반환 받아야 할 경우 사용한다.
inline fun <T, R> T.run(block: T.() -> R): R
// T.run 의 T 는 run 의 수신 객체
// block: T.() 의 T 는 람다의 수신 객체
// R 은 반환 객체


inline fun <R> run(block: () -> R): R
// R 은 run 의 반환 객체
// inline fun <T, R> T.run(block: T.() -> R): R
fun main() {
    var str: String = "TEST code"
    str = str?.run {
        println(this.toUpperCase())
        println(toLowerCase())
        "Complete"
    }
    println(str)
}


// TEST CODE
// test code
// Complete
// inline fun <R> run(block: () -> R): R
fun main() {
    val result = run {
        val items = 10
        val price = 1000
        val discountPercent = 10.0
        ((items * price) * ((100 - discountPercent) * 0.01)).toInt()
    }
    println ("가격 : ${result}원 ")
}


// 가격 : 9000원



with


run 과 같은 동작을 하는 함수로 run 은 수신 객체의 확장 함수이지만 with 는 수신 객체를 파라미터로 받는 함수이다.

  • Nullable 을 허용하지 않는다.
inline fun <T, R> with(receiver: T, block: T.() -> R): R
// receiver: T 의 T 는 with 의 수신 객체
// block: T.() 의 T 는 람다의 수신 객체
// R 은 반환 객체
// inline fun <T, R> with(receiver: T, block: T.() -> R): R
fun main() = with(str) {
    println(toUpperCase())
    println(toLowerCase())
}

private val str: String = "TEST code"


// TEST CODE
// test code



let


run 과 같은 동작을 하는 함수로 run 은 수신 객체 자신을 그대로 전달하지만 let 은 수신 객체를 복사하여 전달하기 때문에 수신 객체에 접근을 할 때 참조 연산자 it 을 사용해야 한다.

  • null 체크 후 코드를 실행하거나 Nullable 한 수신 객체를 다른 타입의 값으로 반환해야 하는 경우 사용한다.
inline fun <T, R> T.let(block: (T) -> R): R
// T.let 의 T 는 let 의 수신 객체
// block: (T) 의 T 는 람다의 파라미터
// R 은 반환 객체
// inline fun <T, R> T.let(block: (T) -> R): R
fun main() {
    val user = User().age?.let {
        var age = 10 + it.age
        age
    }
    println("$user")
}

data class User(
    var name: String = "AAA", 
    var age: Int? = 0
)


// User(name=AAA, age=10)



also


apply 와 동일하게 수신 객체 자신을 반환하는 함수로 let 과 동일하게 수신 객체를 복사하여 전달하기 때문에 수신 객체에 접근을 할 때 참조 연산자 it 을 사용해야 한다.

  • 수신 객체의 내부 프로퍼티 설정뿐만 아니라 유효성 검사와 같은 추가적인 작업 후 수신 객체 자신을 반환해야 하는 경우 사용한다.
inline fun <T> T.also(block: (T) -> Unit): T
// T.also 의 T 는 also 의 수신 객체
// block: (T) 의 T 는 람다의 파라미터
// inline fun <T> T.also(block: (T) -> Unit): T
fun main(user: User) {
    val user = user.also {
        requireNotNull(user.age)
    }
}

data class User(
    var name: String = "AAA", 
    var age: Int = 0
)
essential