코틀린 Scope functions (let, run, with, apply, also)

wonseok·2023년 1월 3일
0
post-thumbnail

Scope functions, Kotlin Document 문서를 참고하여 작성하였다.

let

let 함수는 null-check를 도와주고, 안전하게 코드를 수행하기 위한 local scope를 제공한다.

fun printCar(car: Car?) {
  val isCoupe = car?.let {
    (it.doors <= 2)
  }
  if (isCoupe == true) {
    println("Coupes are awesome")
  }
}

car 인스턴스가 null이라면, safe-call 연산자 (?.) 이후의 코드 블럭은 실행되지 않는다.
또한, let은 어떠한 타입이라도 모두 return할 수 있다.
해당 예시 코드에서는 letBoolean을 반환하며, 차량이 쿠페 차량인지 아닌지 여부를 반환한다.

run

runlet과 많이 유사하지만, 조금 더 target object에 포커스가 되어있다.
코드 블록 내부에서 run은 대상 객체를 this로 전달하고 외부 scope에서 해당 코드 블록을 분리한다.

fun printCar2(car: Car?) {
  val isCoupe = car?.run {
    (this.doors <= 2)
  }
  if (isCoupe == true) {
    println("Coupes are awesome")
  }
}

동일하게 run은 어떠한 타입이라도 모두 return할 수 있다.

그리고 letrun 함수는 모두 “transformational” 함수로도 부를 수 있는데, 반환하는 객체가 함수를 호출하는 객체와 다를 수 있기 때문에 “transformational” 함수라고 부른다.

also

만약 run함수를 also로 대체하고자 한다면, 컴파일 에러를 마주칠 것이다.
letrun 함수(transformation)와는 달리, also 함수는 원래의 객체를 반환해준다.

여기서, 원래의 의미를 변경하지 못한다라고 보면 안된다. 그냥 똑같은 object다.
alsoit을 사용하여 코드 블럭 내에서 객체를 참조하고, this를 사용하여 외부 스코프에서 접근할 수 있다.

fun printCar3(car: Car?) {
  car?.also {
    it.doors = 4
  }.let {
    if (it?.doors != null && it.doors <= 2) {
      println("Coupes are awesome")
    }
  }
}

위 예제 코드에서는, 동일한 car 객체를 반환하므로, it을 사용하여 객체를 변경한 다음, 다른 호출을 연결할 수 있다. 이 예제 코드에서 자동차가 쿠페인지 확인하는 검사는 let 블록 내에 있지만, also 블럭 안에서 4개의 문이 있도록 car 객체가 수정되었으므로, 결론적으로 "Coupes are awesome"을 콘솔에 print하지는 않게 된다.

apply

이쯤되면, apply함수가 어떻게 동작하는지 짐작할 수 있을 것이다.
대상과 동일한 객체를 반환하고, 코드 블록 내에서 this를 사용한다.

fun printCar4(car: Car?) {
  car?.apply {
    doors = 4
  }.let {
    if (it?.doors != null && it.doors <= 2) {
      println("Coupes are awesome")
    }
  }
}

또다시 car 객체가 4개의 문을 갖도록 수정되었으므로, "Coupes are awesome" 문구를 출력하지 않는다.
이러한 함수는 특히 깨끗하고 간결한 코드를 작성하려는 경우 매우 유용하다.

차이점?

scope function들은 서로서로 비슷하기 때문에, 이것들을 구별하는 기준이 있으면 참 좋을텐데, 다행히 공식문서에서는 그 기준을 말해준다.

  • context object를 참조하는 방식
  • return 값

Context object : this or it

scope function 내의 람다에서는, context object는 실제 이름 대신에, 짧은 참조명으로 대체될 수 있다. 여기에서 context object에 접근하는 두 가지 방식이 있는데, 첫 번째는 람다 리시버로 this를 사용하는 방식, 그리고 람다 인자로 it을 사용하는 방식이다.

this

run, with, apply는 대상 객체를 this를 통해 람다 리시버로 참조한다.

it

letalso는 대상 객체를 람다 인자로 참조한다. 만약, 인자의 이름이 정해지지 않았다면, 대상 객체는 기본 이름 값인 it으로 접근될 수 있다.

Return Value

  • applyalso는 대상 객체를 반환한다.
  • let, run, withlambda result를 반환한다.

Context object

applyalso의 return 값은 대상객체 그 자체다. 그래서 이후에 동일한 객체에서 함수 함수 호출을 계속 연결할 수 있다.

val numberList = mutableListOf<Double>()
numberList.also { println("Populating the list") }
    .apply {
        add(2.71)
        add(3.14)
        add(1.0)
    }
    .also { println("Sorting the list") }
    .sort()

Lambda result

let, run, with은 람다 결과를 반환한다.
따라서 결과를 변수에 할당하고, 결과에 대한 작업을 연결하는 등의 작업을 할 때 사용할 수 있다.

val numbers = mutableListOf("one", "two", "three")
val countEndsWithE = numbers.run { 
    add("four")
    add("five")
    count { it.endsWith("e") }
}
println("There are $countEndsWithE elements that end with e.")

0개의 댓글