Kotlin Scoping Functions - apply, also, run, let, with

WellBeing-Man·2021년 4월 22일
0

본 게시글은 해당 글을 보고 요약한 것입니다.

Kotlin Scoping Functions apply vs. with, let, also, and run

🗣 무엇을 하려고 하나

코틀린에서는 5가지의 확장함수를 제공합니다.
각각의 정의의 공통점과 차이점을 살펴본 후 언제, 어떻게 사용할지 알아봅시다.

😬 정의의 공통점과 차이점

일단 각 함수의 정의는 다음과 같다.

inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    return receiver.block()
}
inline fun <T> T.also(block: (T) -> Unit): T {
    block(this)
    return this
}
inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}
inline fun <T, R> T.let(block: (T) -> R): R {
    return block(this)
}
inline fun <T, R> T.run(block: T.() -> R): R {
    return block()
}

정의의 차이점을 살펴보자면
1. 명시적인 파라메터로 전달되는 Receiver vs 암시적인 Reciever

  1. 람다식에 Receiver가 명시적으로 전달 vs 암식적으로 전달

  2. Receiver를 리턴 vs 람다식의 결과를 리턴

친절하게도 표로 정리도 되어있다.

😬 사용하기

1. apply()

언제?

  • 람다식 내부에 Receiver가 필요하지 않고, Receiver를 그대로 리턴하고 싶을 경우에 사용한다.
  • 새로운 객체를 생성할때 사용하면 좋다.

어떻게?

val peter = Person().apply {
    // only access properties in apply block!
    name = "Peter"
    age = 18
}

만일 apply()를 사용하지 않는다면 다음과 같습니다.

val clark = Person()
clark.name = "Clark"
clark.age = 18

2. also()

언제?

  • 람다식에서 Receiver를 전혀 사용하지 않거나.
  • 람다식에서 Receiver의 속성을 변경하지 않고 사용할때 사용한다.
  • 람다식이 Receiver와 다른 값을 리턴해야하는 경우는 사용할 수 없다.
  • 프로퍼티를 할당하기 전 데이터의 유효성을 확인하거나, 객체의 사이드 이펙트를 확인할 때 유용하다.

어떻게?

class Book(author: Person) {
    val author = author.also {
      requireNotNull(it.age)
      print(it.name)
    }
}

만일 also()를 사용하지 않는다면 다음과 같습니다.

class Book(val author: Person) {
    init {
      requireNotNull(author.age)
      print(author.name)
    }
}

3. let()

언제?

  • 전달된 값이 null이 아닌경우, 코드를 실행 해야할때
  • Nullable한 객체를 다른 Nullalbe한 객체로 변환해야할때
  • 단일 지역변수를단일 지역변수의 범위를 제한할때 사용한다.

어떻게?

getNullablePerson()?.let {
   // null이 아닐때만 실행된다.
   promote(it)
}
val driversLicence: Licence? = getNullablePerson()?.let {
   // getNullablePerson을 nullable한 driversLicence로 변환
   licenceService.getDriversLicence(it) 
}
val person: Person = getPerson()
getPersonDao().let { dao -> 
   // 변수 dao는 이 블록안으로 제한
   dao.insert(person)
}

만일 let()를 사용하지 않는다면 다음과 같습니다.

val person: Person? = getPromotablePerson()
if (person != null) {
  promote(person)
}
val driver: Person? = getDriver()
val driversLicence: Licence? = if (driver == null) null else
    licenceService.getDriversLicence(it)
val person: Person = getPerson()
val personDao: PersonDao = getPersonDao()
personDao.insert(person)

4. with()

언제?

  • Non-nullable Receiver일때, 결과가 필요하지 않을때

어떻게?

val person: Person = getPerson()
with(person) {
   print(name)
   print(age)
}

만일 with()를 사용하지 않는다면 다음과 같습니다.

val person: Person = getPerson()
print(person.name)
print(person.age)

5. run()

언제?

  • 어떤 값을 계산하고 싶을때
  • 여러 지역변수의 범위를 제한하고 싶을때 사용한다.
  • 명시적인 매개변수를 암시적인 Receiver로 변환할때 사용한다.

어떻게?

   val inserted: Boolean = run {
       val person: Person = getPerson()
       val personDao: PersonDao = getPersonDao()
       personDao.insert(person)
   }
   fun printAge(person: Person) = person.run {
       print(age)
   }

만일 run()를 사용하지 않는다면 다음과 같습니다.

val person: Person = getPerson()
val personDao: PersonDao = getPersonDao()
val inserted: Boolean = personDao.insert(person)
fun printAge(person: Person) = {
    print(person.age)
}

6. Scoping function 중첩하여 사용하기

  • 중첩해서 사용하면 코드를 읽기가 매우 힘들어 질 수 있다.
  • 람다식에 Reciever가 암시적으로 전달되는 apply,with,run은 중첩해서 쓰면 안된다.
  • also 와 let을 중첩해서 사용할때는 람다식의 암시적인 매개변수를 it으로 쓰지말고 명시적으로 제공해야한다.

7. chaining

  • 체이닝을 사용하면 오히려 가독성이 좋아진다.
// SQL 준비, SQL 로깅, SQL 실행, 그리고 insert가 성공여를  
//나타내는 Boolean값을 리턴한다.

private fun insert(user: User) = SqlBuilder().apply {
  append("INSERT INTO user (email, name, age) VALUES ")
  append("(?", user.email)
  append(",?", user.name)
  append(",?)", user.age)
}.also {
  print("Executing SQL update: $it.")
}.run {
  jdbc.update(this) > 0
}
profile
건강맨

0개의 댓글