카샤의 만개시기

Kotlin Scope Functions (apply, with, let, also, run, use) 본문

Kotlin

Kotlin Scope Functions (apply, with, let, also, run, use)

SKaSha 2019. 12. 8. 03:50

코틀린의 스코프 함수는 다음 5가지 함수를 제공하며 기본적으로 매우 비슷하다.
apply, with, let, also, run
추가적으로 use 함수도 제공한다.

스코프 함수는 receivercode block을 받아 제공된 코드 블럭을 제공된 수신자에서 실행한다.
자바의 함수형 인터페이스(Funtional Interface)와 비슷하다고 보면 된다.

scope functions

함수 이름 람다식의 접근 방법 반환 방법
let it block 결과
also it T caller (it)
apply this T caller (this)
run this block 결과
with this block 결과

let 함수

public inline fun <T, R> T.let(block: (T) -> R): R { ... return block(this) }

with 함수를 제외한 나머지 함수들은 제네릭의 확장 함수 형태이므로, 어디든 적용할 수 있습니다.

val score: Int? = 32

score?.let {  println("$it") }    // 1번
val str = score.let { it.toString() }     // 2번
score?.let {  println("$it") } ?: println("0")    // 3번
score?.let { it + 2 }.let {     // 4번
    val i = it + 5
    i     // 마지막 식 반환
}

1번에서는 세이프 콜의 결과값이 null이 아닐 경우 block이 실행됩니다.
2번에서는 세이프 콜을 사용하지 않았는데 만약 score 값이 null일 경우 str에는 null이 할당 됩니다.
세이프 콜을 사용하더라도 코드 블럭인 람다식을 사용하지 않게 되므로 str은 String?으로 추론되어 null이 할당됩니다.
3번에서는 엘비스 연산자를 이용하여 score가 null일 경우 0이 출력되도록 하였습니다.
4번에서는 메서드 체이닝을 통하여 함수를 연속적으로 호출하였습니다.

also 함수

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }

also 함수는 let 함수와 비슷하나, 코드 블럭 수행 결과와 상관없이 T 객체 this를 반환합니다.

var m = 1
m = m.also { it + 3 }
println(m)  

위 코드에서 also 코드 블럭은 원본 값인 1이 출력 됨.

기존의 코드
fun makeDir(path: String): File {
    val result = File(path)
    result.mkdirs()
    return result
}

// 스코프 함수로 개선된 함수
fun makeDir(path: String) = path.let { File(it) }.also{ it.mkdirs() }

apply 함수

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

apply 함수는 also 함수와 비슷하나, 코드 블럭의 매개변수가 T.()와 같이 확장함수로 처리된다는 것입니다.

fun makeDir(path: String) = File(path).apply { mkdirs() }

위 예제에서 사용했던 makeDir 함수를 apply 함수를 이용하여 수정하였습니다.

run 함수

public inline fun <R> run(block: () -> R): R = return block()
public inline fun <T, R> T.run(block: T.() -> R): R = return block()

run 함수는 다음 2가지 형태로 사용할수 있습니다.

  1. 인자가 없는 익명 함수처럼 동작하는 형태
  2. 객체에서 호출하는 형태
// 1번 형태
var str = run {
    val str = "Kotlin"
    str    // 마지막 표현식 반환
}

// 2번 형태
var str = "Kotlin"
str.run {
    print("$this")
    "Spring"
}

with 함수

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

with 함수는 run 함수와 기능이 비슷한데, run 함수는 receiver가 없지만 with 함수는 receiver로 전달할 객체를 처리하므로 객체의 위치가 달라집니다.
with는 확장 함수 형태가 아니므로, 단독으로 사용되는 함수이며 세이프 콜(?.)을 지원하지 않아 let 함수와 같이 사용되기도 합니다.

data class User(val name: String?)
val user= User()

var result = with(user) {
    name = "KaSha"
}

use 함수

public inline fun <T: Closeable?, R> T.use(block: (T) -> R): R

use 함수는 오류 발생 여부와 상관 없이 항상 close() 호출을 보장해줍니다.
그러므로 T 객체는 Closeable?을 상속 받고 있어야 합니다.

File(path).bufferedReader().use {
    println(it.readText())
}

참고

서적 : Do it! 코틀린 프로그래밍
이미지 : https://medium.com/@fatihcoskun/kotlin-scoping-functions-apply-vs-with-let-also-run-816e4efb75f5

Comments