3 분 소요

 

스코프 함수는 특정 코드 블록을 객체의 컨텍스트 내에서 실행하기 위해 고안된 함수입니다. 스코프 함수의 람다함수는 일시적인 스코프를 형성있습니다. 이 스코프 안에서는 객체의 이름 없이 접근할 수 있습니다. 결국 전에 없던 새로운 기능을 제공한다기 보다는 간결성과 가독성을 올려주는 장치입니다.

5가지 스코프 함수

코틀린은 5가지의 스코프 함수를 제공합니다. 5가지 모두 같은 기능을 제공하지만, ① 객체 참조 방식과 ② 반환값에 차이가 있습니다.

그림입니다. 원본 그림의 이름: CLP00004080b47a.bmp 원본 그림의 크기: 가로 1231pixel, 세로 654pixel

① 객체의 참조방식에는 itthis가 있습니다. 이 둘은 형태만 다를 뿐 같은 역할을 합니다. it은 lambda argument로, Trailing Lambda의 it과 똑같이 사용됩니다. 즉, 매개변수가 하나일 때, ‘매개변수 ->’을 생략하고 it을 매개변수 대신 활용합니다. this는 lambda receiver로 리시버 객체를 가리킨다. this는 생략이 가능합니다.

② 반환값에는 lambda resultcontext object가 있습니다. lambda result는 람다식의 결과로 마지막 문장이나 표현식에 의해 결정된 값을 의미합니다. context object는 스코프 함수를 호출한 객체를 의미합니다. 다른 메서드들을 바로 호출하는 메서드 체이닝에 활용됩니다.

참조 방식과 반환값으로 분류해본다면 다음과 같습니다. 위와 같은 분류에 with는 확장함수만 가능하기 때문에 뺐습니다

  Lambda result context object
it let also
this run apply

다음은 코틀린 공식 문서에서 제안한 사용법입니다. 요약해보자면 다음과 같습니다.
1️⃣ with: 함수 호출을 그룹화하기
2️⃣ let: non-null 객체에서 람다 실행, 지역 스코프에서 표현식을 변수로 도입
3️⃣ run: 표현식이 필요한 곳에서 문장 실행 || extension-run: 객체 선언 및 결과 계산
4️⃣ apply: 객체 선언
5️⃣ also: 부가 효과

1. with -> with this object, do the following

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

▶ with 함수는 다른 scope 함수와 달리 확장함수가 아니기 때문에 context object를 인수로 전달해야합니다.

▶ 전달된 context object는 scope 내에서 this로 사용 가능합니다.

1) context object의 메서드들은 사용하지만, 뭔가를 반환할 필요가 없을 때 사용합니다.

fun main(args: Array<String>) = with(BufferedReader(InputStreamReader(System.`in`))) { 
    val input = readLine()
}
// 아래의 코드를 위의 코드와 같이 사용하여 메서드 함수를 내장함수처럼 사용할 수 있습니다.
fun main(args: Array<String>) {
    val br = BufferedReader(InputStreamReader(System.`in`))
    val input = br.readLine()
}

2) helper 객체로 사용될 수도 있습니다. 어떤 객체의 프로퍼티나 함수들을 값을 계산하는 데에 사용될 때 helper 객체라고 합니다.

val numbers = mutableListOf("one", "two", "three")
val firstAndLast = with(numbers) {
    // 아래의 값을 계산하기 위한 용도로 사용되었습니다.
    "The first element is ${first()}," +
    " the last element is ${last()}"
}
println(firstAndLast)


2. let ->

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

● it으로 context object를 사용할 수 있습니다

● lambda result를 반환합니다

1) null을 체크할 때 사용됩니다.

val str: String? = "Hello"   
//processNonNullString(str)       ->  compilation error: str can be null

val length = str?.let { 
    // 세이프콜 연산자( ?. )와 함께 사용하여 객체의 값을 가지고 코드 블록을 실행합니다.
    println("let() called on $it")        
    processNonNullString(it)      // OK: 'it' is not null inside '?.let { }'
    it.length
}

2) 객체의 결과값에 하나 이상의 함수를 호출하는 경우 사용됩니다.

val numbers = mutableListOf("one", "two", "three", "four", "five")
val resultList = numbers.map { it.length }.filter { it > 3 }
println(resultList)    

val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map { it.length }.filter { it > 3 }.let { 
    println(it)
    // and more function calls if needed
} 

3) 제한된 scope에서 사용될 지역 변수를 위해 사용합니다.

val numbers = listOf("one", "two", "three", "four")
// first()로 0번 element를 let 스코프 내에서 지역변수처럼 사용합니다.
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'")


3. run -> run the code block and compute the result

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

● this로 context object를 사용할 수 있습니다

● lambda result를 반환합니다

1) 람다식이 객체를 초기화하고 반환값을 계산하는 경우

val service = MultiportService("https://example.kotlinlang.org", 80)

val result = service.run {
    port = 8080
    query(prepareRequest() + " to port $port")
}
inline fun <T, R> T.run(block: T.() -> R): R

2) 여러 문장을 실행한 결과로 한 표현식을 얻어야 하는 경우

// 이후에 사용하지 않을 변수를 스코프 내에 선언합니다. 결과에 대한 자세한 설명을 하여 가독성을 높입니다. -> Regex(표준문자열)에 대한 설명을 했습니다.
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)
}


4. apply -> apply the following assignments to the object

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

● this로 context object를 사용할 수 있습니다

● context object를 반환합니다

1) 객체의 초기화에 사용됩니다.

// this는 생략이 가능하므로, 편리하게 초기화합니다.
val adam = Person("Adam").apply {
    age = 32
    city = "London"        
}
println(adam)


5. also -> and also do the following with the object

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

● it으로 context object를 사용할 수 있습니다

● context object를 반환합니다

1) context object를 인수로 사용할 때 (프로퍼티나 메서드보다 그 자체를 참조할 필요가 있을 경우)

// 객체를 반환하므로, 이후의 메서드 호출을 할 수 있으므로, 가독성이 높아집니다.
val numbers = mutableListOf("one", "two", "three")
numbers
    .also { println("The list elements before adding new one: $it") }
    .add("four")

Reference

  • https://kotlinlang.org/docs/scope-functions.html#context-object-this-or-it
  • https://hyeon9mak.github.io/using-kotlin-scope-function-correctly/
  • 댓글남기기