Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

개발자이야기

kotlin scope functions (let,run,with,apply,also) 본문

kotlin

kotlin scope functions (let,run,with,apply,also)

개발자가되고싶어 2021. 6. 1. 22:37
kotlin scope functions

 

코틀린 범위지정 함수 5가지에 대해 알아 보겠습니다.

보통 let ,with, apply 를 많이 써왔지만 명확한 기준을 잡고 싶어 한번 분석해 보려고 이 글을 작성합니다.

해당글은 코틀린 공식 문서의 예제를 기반으로 분석및 작성 합니다.

 

let

Kotlin 표준 라이브러리 함수 let은 범위 지정 및 null 검사에 사용할 수 있습니다. 객체에서 호출되면 람다식의 코드 블록을 실행하고 마지막 표현식의 결과를 반환합니다. 객체는 참조 it(기본적으로) 또는 사용자 정의 이름으로 블록 내에서 액세스 할 수 있습니다.

위 내용은 kotlin 공식문서에서 설명해 주는 내용입니다.

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

먼저 let이 정의되어 있는 코드를 한번 살펴보겠습니다.

함수의 형태는 확장함수 형태로 되어 있습니다. (T.let)

let 함수 내부에 있는 return block(this) 구문을 한번 확인해 보겠습니다.

다른 함수들의 return을 생각해봅시다. 수신객체인 this(apply, also) 를 반환하는가 또는 위의 let과 같이 코드블록을 실행하여 결과값을 반환하는가에 따라 사용법을 판단하는데 편합니다.

1.   val empty = "test".let {               
2.      println(it)                        
3.      it.isEmpty()  <- 결과 반환                           
     }

위의 예제 코드를 한번 살펴봅시다. 해당 코드는 단순 범위지정으로만 사용하고 있습니다.

 

1. 확장함수 호출

첫번째로 "test".let let 확장 함수를 호출했습니다. let의 확장함수 내부에 있는 return block(this) 구문을 한번 확인해 보겠습니다. block(this) 람다 함수 (block: (T) -> R) 를 호출했습니다. 이젠 람다식 코드 블록 으로 넘어가봅시다.

 

2. 

람다식 내부에선 전달받은 파라미터 it("test")  사용할 수 있습니다.

 

3. 반환값

블록 내 마지막 표현식이 반환 값으로 간주됩니다. empty의 변수엔 해당 구문으로 인해 false 값이 할당 됐습니다.

이렇게 코드블록 마지막 표현식을 반환하며 사용할 경우 입맛대로 필요한 값을 얻어 사용하는데 편합니다.

 

val str: String? = "Hello"   

val length = str?.let { 
    println("let() called on $it")        
    processNonNullString(it)
    it.length   <- 결과 반환     

kotlin 공식 문서에서 소개하고 있는 let의 두번째 예제입니다. 여기선 str?.let 널 체크를 추가하여 사용하고 코드블록 마지막 표현식을 반환하여 lenth 값을 구하는데 사용합니다.

 

   

run
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

let과 마찬가지로 run은 표준 라이브러리의 또 다른 범위 지정 기능입니다. 기본적으로 동일한 작업을 수행합니다. 코드 블록을 실행하고 그 결과를 반환합니다. 객체의 메서드를 호출하려는 경우에 유용합니다.

 

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

let 함수와 차이점을 봅시다. 차이는 수신객체 T 를 확장함수로 전달 하느냐의 차이입니다.

fun getNullableLength(ns: String?) {
    val length = ns?.run {                                                 
        println("\tis empty? " + isEmpty())                   
        println("\tlength = $length")                           
        length    <- 결과 반환                                            
    }
}

 

이로 인해 run 함수는 this를 사용할수 있고 위와같이 중복된 코드를 제거할 수 있습니다. (this 생략)

 

val sum = run {
	val first = 5
	val second = 10
	"더한값은"+(first+second) <- 결과 반환
}

위와같이 특정 변수의 범위지정을 하여 사용할 수도 있습니다.

 

with

with는 파라미터의 멤버에 간결하게 액세스 할 수 있습니다. 멤버를 참조 할 때 인스턴스 이름을 생략 할 수 있습니다.

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

with 함수는 수신 객체(receiver: T) 를 파라미터로 받아 확장 함수로 람다 함수를 호출하고 있습니다.

해당 함수는 아래의 코드와 같이 인스턴스 이름을 생략하여 사용할 수 있습니다.

val car = Car()
with(car) {
    start()
    stop()
}

 

val numbers = mutableListOf("하나", "둘", "셋")
val firstAndLast = with(numbers) {
	"마지막 값은 ${last()}"   <- 결과 반환   
}
println(firstAndLast)

위의 코드처럼 사용할수도 있습니다. 마지막 표현식이 반환되어 사용됩니다.

 

 

 

apply, also
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

위의 also, apply 함수를 먼저 확인해보면 함수의 return 값이 수신객체 this를 반환하고 있습니다. 

그렇기 때문에 해당하는 두개의 함수는 람다 결과의 반환값을 돌려줄수 없으며 수신객체 자체를 돌려주어 사용하게 됩니다.

 

 

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

공식문서에 따르면 apply는 위와같이 객체의 초기화에 사용하면 유용하다고 말하고 있습니다.

 

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

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

그리고 위와 같이 chain 형태로 연결해 사용할수도 있습니다.

 

위의 함수들의 몇몇은 서로 교체되어 사용할 수 있습니다. 함수의 return 값이 람다식의 결과값 일지라도 람다식 코드블록 내에 마지막 표현식을 반환하여 return해 주기 때문에 아래와같이 사용해도 되긴 합니다.

val car = with(Car()) {
	engine = "가솔린"
	tier = "겨울타이어"
	this
}

하지만 각 함수마다 가이드가 있는 만큼 위와 같은경우 apply를 사용합시다. 

 

글을 작성하며 let, run, with, apply 는 유용한 함수로 사용할것 같습니다. 하지만 also는 안드로이드 개발자인 저로서는 아직도 어디에 써야할지 아리송합니다. apply를 사용하여 객체의 초기화가 잘 이루어 졌는지 Log를 찍어 확인할때 써야될것 같습니다

 

각 함수들을 살펴보면 함수 내의 return 값이 수신객체를 리턴하는가와 람다식의 결과를 리턴 하는가에 따라 사용법이 달라지는것을 확인할 수 있었습니다.

 

공부하며 작성한 글입니다. 추가적으로 알게되는 내용은 수정 및 업데이트를 할 예정입니다.

봐주셔서 감사합니다.

'kotlin' 카테고리의 다른 글

kotlin coroutine  (0) 2021.08.24
kotlin data model 쉽게 만들기  (0) 2021.08.02
kotlin map, flatMap  (0) 2021.06.25
kotlin 고차함수  (1) 2021.06.06