[Kotlin] 스코프 함수(Scope functions)

jeunguri·2022년 9월 19일
0

kotlin

목록 보기
2/5


let, run, with, apply, also 5가지로 객체 생성시 사용하는 함수들이다. 자체적인 scope를 갖는 코드블럭을 사용해서 scope function이라 불린다.

공식문서 참고

FunctionObject referenceResult valueIs extension function
letitLambda resultYes
runthisLambda resultYes
withthisLambda resultNo: takes the context object as an argument
applythisContext objectYes
alsothisContext objectYes



with

코드들을 with에 객체를 명시하고 하나의 스코프로 묶어 dot notation 없이 사용할 수 있다. With을 사용해 블럭으로 묶으면 가독성이 좋아지고 코드상에서 반복하던 dot notation을 제거해서 사용이 간편해진다.
즉, 객체의 메소드 여러개를 한번에 사용하거나 속성을 한번에 설정할 때 사용하기 좋다.

// with를 사용하지 않은 경우
fun handle() {
   binding.progressBar.isVisible = true
   binding.recyclerView.isGone = true
}

// with를 사용한 경우
fun handle = with(binding) {
   progressBar.isVisible = true
   recyclerView.isGone = true
}



let

람다함수이기 때문에 특별히 명시하지 않으면 객체는 it으로 사용할 수 있다. 아래 예제에서는 binding으로 명시해주었기 때문에 이를 이용하고 있다.

코드블럭 내에서 it 이나 binding 처럼 매번 객체를 표시해줘야 하기 때문에 with가 가능한 상황에서는 굳이 let을 사용할 필요가 없다.
let 을 주로 사용하는 경우는 null 체크를 간편하게 하기위할 때이다. null 체크 후, 실행을 let을 이용해 대체 가능하다.

val length = str?.let {
     println(“called on $it”)
     it.length
}

str가 null이면, let 블럭은 실행되지 않는다. 즉, it(str != null) { } 과 동일한 효과를 갖는다.

또한, context object를 다른값으로 변환하는 경우, 이를 local scope로 묶어 사용할 수 있다. 변수는 local scope 안에서만 유효하므로, 외부에서 잘못 사용하거나 이름이 겹치는 등의 고민을 안해도 된다.
아래와 같이 사용하면, 외부에서는 firstItem에 대해 신경쓰지 않고 오로지 변환된 modifiedFirstItem만 참조해서 사용할 뿐이다.

val numbers = listOf("one", "two", "three", "four")
val modifiedFirstItem = numbers.first().let { firstItem ->
    println("The first item of the list is '$firstItem'")
    if (firstItem.length >= 5) firstItem else "!" + firstItem + "!"
}.uppercase()
println("First item after modifications: ‘$modifiedFirstItem'")



apply

람다함수 블럭내에서는 리턴값을 신경쓰지 않고, apply 뒤에 dot notation을 추가로 이어갈 수 있다. 객체는 this로 참조되어 with와 동일하게 블럭 내에서 객체를 명시적으로 표시할 필요가 없다.

val adam = Person("Adam").apply {
    age = 32
    city = "London"        
}
println(adam)

with는 인자로 context object인 객체를 넘겨주지만, apply는 extension function 형태이기 때문에 객체를 생성해 할당하기 전에 사용할 수 있다. 위의 예제에서 Person 객체는 adam에 할당되기 이전에 apply 블럭 내의 값들이 채워지고 나서 adam에 할당된다. 따라서 객체의 생성 시점에서 객체의 초기화에 많이 사용된다.





also

also는 extension function으로 정의되는 람다함수이고 리턴값은 없다. 즉, apply와 동일하게 람다함수 블럭 내에서 특별히 리턴값을 신경쓰지 않고, dot notation을 이용해 빌더패턴처럼 사용이 가능하다.
차이점은 apply의 경우 람다함수에 인자를 넘겨주지 않기 때문에 this로 참조해야 하지만, also는 context object T를 인자로 넘겨주기 때문에 it을 사용하거나 다른 명시적인 이름으로 참조가능하다.

val numbers = mutableListOf("one", "two", "three")
numbers
    .also { println("The list elements before adding new one: $it") }
    .add("four")



run

run은 context object가 없이 단일 함수로 되어 있는 것과, extension function으로 정의된 두가지 버전이 있다. 둘 다 람다함수의 리턴값을 사용한다.

  • Context object의 extension function으로 사용되는 경우
val service = MultiportService("https://example.kotlinlang.org", 80)

val result = service.run {
    port = 8080
    query(prepareRequest() + " to port $port")
}

// the same code written with let() function:
val letResult = service.let {
    it.port = 8080
    it.query(it.prepareRequest() + " to port ${it.port}")
}

사실상 with와 동일하나 extension function 형태라는게 다를 뿐이다.
따라서 run을 사용하면 객체를 생성하는 시점에 객체를 변수에 할당하지 않고 먼저 사용 가능하다. 또한, apply나 also는 context object를 리턴하지만 run에서는 람다함수의 리턴값이 사용되므로 임의로 생성한 값이나 객체를 리턴할 수 있다.

  • Context object를 사용하지 않는 경우
val hexNumberRegex = run {
    val digits = "0-9"
    val hexDigits = "A-Fa-f"
    val sign = "+-"

    Regex("[$sign]?[$digits$hexDigits]+")
}

for (match in hexNumberRegex.findAll("+123 -FFFF !%*& 88 XYZ")) {
    println(match.value)
}

0개의 댓글