본문 바로가기

프로그래밍언어/Kotlin

[Kotlin] 영역 함수 (Scope functions)

반응형

코틀린은 특정 객체의 컨텍스트안에서 코드 블럭을 실행하도록 하는 함수들을 제공한다. 특정 객체에 대해서 영역 함수 (scope function) 들을 사용하면 임시적으로 영역 (scope) 형성되며, 영역안에서는 객체의 이름이 없이 컨텍스트 객체에 접근이 가능하다. scope function 에는  let, run, with, apply, also, 이렇게 5 함수들이 포함되며 이러한 함수들을 사용하여 코드를 더욱 간결하고 가독성 좋게 작성할 있다.

1. Scope functions 비교

scope functions 기본적으로 코드 블럭을 실행한다는 점에서 동일하지만, 블럭안에서 컨텍스트 객체에 접근하는 방법과 표현식의 결과 등이 서로 다르다.
 

코틀린 영역함수 (https://kotlinlang.org/docs/scope-functions.html#function-selection)

- Context object: this or it

scope function 전달된 람다식 내부에서는 컨텍스트 객체를 이름 대신 짧은 참조를 통해 접근이 가능하다. 함수들은 lambda receiver (this) lambda argument (it) 두가지 방식 하나를 사용한다. 방식 모두 동일한 기능을 제공하기 때문에 사용하는 상황에 유리한 방식을 선택하여 사용할 있다.

1) this

run, with, apply 는 컨텍스트 객체를 lambda receiver (this) 로 참조한다. 따라서 함수안에서 일반 클래스 함수와 같이 객체를 사용할 수 있다.

 

대부분의 경우 컨텍스트 객체의 멤버에 접근할 때 this 를 생략할 수 있는데, 이러한 경우에 외부의 객체나 함수들과 구분이 어렵기 때문에 생략하는 것을 추천하지는 않는다.

 

val adam = Person("Adam").apply {
    age = 20        // same as this.age = 20
    city = "London"
}
println(adam)

2) it

let, also 는 컨텍스트 객체를 lambda argument 을 통해 참조한다. 만약 인자의 이름이 선언되지 않았다면 기본적으로 'it' 이라는 이름을 기본값으로 가지게 된다.

 

fun getRandomInt(): Int {
    return Random.nextInt(100).also {
        writeToLog("getRandomInt() generated value $it")
    }
}

val i = getRandomInt()
println(i)

 

아래의 예시는 'it' 을 사용하는 것이 아니라 람다식 인자의 이름을 'value' 라고 따로 선언하여 사용한 코드이다.

 

fun getRandomInt(): Int {
    return Random.nextInt(100).also { value ->
        writeToLog("getRandomInt() generated value $value")
    }
}

val i = getRandomInt()
println(i)

- Return value

scope functions 는 반환값에서도 차이를 가지고 있는데, apply also 는 컨텍스트 객체를 반환하고, let, run, with 은 람다식의 결과를 반환한다.

1) context object

apply also 는 결과로 컨텍스트 객체 자체를 반환한다. 그렇기 때문에 이 함수들은 함수 결과에 다시 함수를 호출하는 체이닝 함수 호출이 가능하다.

 

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()

2) lamdba 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.")

2. Scope Functions

- let

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

 

let 함수는 컨텍스트 객체를 argument (it) 으로 참조하고 람다식 결과를 반환한다.

 

let 은 함수 호출 체인에 이어서 호출할 수 있다. 아래의 예제는 filter() 함수 뒤에 let 을 호출하여 실행하는 에제이다. 만약 let 에 포함되는 코드 블럭이 'it' 을 인자로 사용하는 단일 함수만 존재한다면 람다식 대신 메서드 참조자인 '::' 를 사용하여 호출할 수 있다.

 

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

numbers.map { it.length }.filter { it > 3 }.let(::println)

 

let 은 주로 non-null values 를 포함하는 코드 블럭을 실행하는데 사용된다. non-null 객체에 대해 작업을 수행하기 뒤해서는 safe call 연산자인 '?.' 를 사용하고 let 함수를 호출하도록 한다.

 

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
}

- with

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

 

with 함수는 컨텍스트 객체에 receiver (this) 으로 참조하고 람다식 결과를 반환한다.

 

with extension function 이 아니기 때문에 객체에 '.' 으로 호출할 수 없다. 아래의 예제와 같이 컨텍스트 객체를 인자로 받아서 수행하는데, 람다식 안에서는 this 로 객체를 참조할 수 있다. with 함수는 함수의 반환 결과를 사용하지 않는 경우에 사용하는 것을 추천한다. 코드에서 with "이 객체를 이용하여 다음을 수행합니다." 라고 읽을 수 있다.

 

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    println("'with' is called with argument $this")
    println("It contains $size elements")
}

- run

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

 

run 함수는 컨텍스트 객체에 receiver (this) 으로 참조하고 람다식 결과를 반환한다.

 

run   with 과 동일하지만 extension function 이기 때문에 객체에 '.' 으로 호출할 수 있다. run 함수는 객체를 초기화와 반환값을 연산 두가지 모두에 유용하다.

 

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}")
}

 

run 함수는 non-extension 함수로도 사용할 수 있다. non-extension 으로 사용하는 경우 컨텍스트 객체는 없지만 여전히 람다식 결과를 반환할 수 있다. run 은 코드상에서 "코드 블럭을 실행하고 결과를 계산한다" 라고 읽을 수 있다.

 

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

 

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}")
}

- apply

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

 

apply 함수는 컨텍스트 객체를 receiver (this) 로 참조하고 객체 자체를 반환한다.

 

apply 는 컨텍스트 객체를 결과값으로 반환하기 때문에 값을 반환하지 않고 수신 객체의 멤버들에 대해서 동작하는 코드 블럭에 사용하는 것을 추천한다. 주로 객체의 설정과 같은 상황에 사용된다. 이때문에 apply "객체에 다음 할당을 적용한다" 라는 의미를 가진다.

 

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

- also

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

 

also 함수는 컨텍스트 겍체를 argument (it) 으로 참조하고 객체 자체를 반환한다.

 

also 는 컨텍스트 객체를 인자로 받아서 수행하는 작업에 유용하다. 객체의 속성이나 함수보다는 객체 그 자체를 참조하는 구문에 사용하는 것을 추천한다.

 

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

3. takeIf and takeUnless

추가적인 scope function 으로 기본 라이브러리는 takeIf takeUnless 함수를 제공하고 있다. 이 함수들은 함수 호출 체인에서 객체의 상태를 체크하는데 사용된다.

 

fun<T>T.takeIf(predicate:(T)->Boolean):T?
fun<T>T.takeUnless(predicate:(T)->Boolean):T?

 

객체에서 takeIf 를 호출하면, takeIf 내부의 로직을 수행하여 이를 만족하면 해당 객체를 반환하고 아닌 경우 null 을 반환한다. takeUnless takeIf 의 반대로 로직을 만족하면 null 을 그렇지 않으면 객체를 반환한다.

 

val number = Random.nextInt(100)

val evenOrNull = number.takeIf { it % 2 == 0 }
val oddOrNull = number.takeUnless { it % 2 == 0 }
println("even: $evenOrNull, odd: $oddOrNull")

[Reference]

 

Scope functions | Kotlin

 

kotlinlang.org

 

[Kotlin] 코틀린 let, with, run, apply, also 차이 비교 정리

let, with, run, apply, also 코틀린에는 이렇게 생긴 확장함수들이 있다. 객체를 사용할 때 명령문들을 블럭{} 으로 묶어서 간결하게 사용할 수 있게 해주는 함수들이다. 문제는 서로 비슷비슷해서 헷

blog.yena.io

 

반응형