핀수로그
  • [Kotlin] Scope Function 범위 지정 함수
    2023년 12월 09일 11시 50분 42초에 업로드 된 글입니다.
    작성자: 핀수
    728x90
    반응형

    들어가며

    처음 코틀린을 공부했을 때, 스코프 함수를 만나고 이것이 코틀린 진입장벽 중 하나라고 생각했다.

    생전 처음 보는 키워드에다 (본인은 자바를 사용하고 있었다.) 어디에 뭘 써야하는지 도무지 감이 오지 않았기 때문이었다.

    스코프함수에 대해 공부를 했지만 시간이 흐른 뒤에야 고백하자면 100% 이해하고 사용한 것이 아니었다.

    요즘같이 기본을 탄탄하게 다져야겠다는 생각이 드는 바로 지금, 코틀린의 스코프 함수를 다시 알아보기로 한다.

     

    Scope Function

    (이하 스코프 함수)

    The Kotlin standard library contains several functions whose sole purpose is to execute a block of code within the context of an object. When you call such a function on an object with a lambda expression provided, it forms a temporary scope. In this scope, you can access the object without its name. Such functions are called scope functions. There are five of them: let, run, with, apply, and also.

     

    코틀린 공식 문서의 스코프 함수에 대한 설명이다. 이를 좀 더 알아보면

    스코프 함수의 목적은 객체의 컨텍스트 내에서 코드 블록을 실행하는 것이다.

    그 실행된 코드 블록에서는 이름 없이 개체에 액세스를 할 수 있고, 이러한 함수를 스코프 함수라고 한다는 것이다.

    그리고 스코프 함수에는 let, run, with, apply, also가 있다.

     

    함수형 프로그래밍을 쉽게 적용할 수 있도록 도와주는 친구들인 것이다,,

     

    구성요소

    코틀린의 스코프 함수들은 두가지 구성요소를 가진다.

    • 수신 객체
    • 수신 객체 지정 람다

    여담이지만 언어나 기술 요소를 공부할 때 이런 어려운 말들이 개념을 이해하는 데에 방해가 된다고 생각한다.

    그러니까 수신 객체가 뭔지부터 한번 살펴보자.

    수신객체

    말 그대로 수신하는 객체일 것이다...수신객체를 이해하려면 코틀린의 확장함수에 대해 알고 있어야 한다.

    fun String.addDot() = "${this}."
    
    fun main() {
        println("Hello".addDot()) // Hello.
    }

     

    위의 addDot() 함수를 확장함수라고 부르는데, 코드 내의 this 부분이 바로 수신객체이다.

    정확하게 의미를 설명하면 확장 함수가 호출되는 대상이 되는 값(객체)이다.

    수신 객체 지정 람다

    말 그대로 수신 객체를 지정하는 람다식이라는 뜻이다. 자세한 것은 스코프 함수를 알아보며 이해하자.

     

    with

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

     

    with 의 정의를 살펴보면 receiver와 block을 파라미터로 받고 있다.

    receiver가 아까 살펴본 수신 객체, block 이 수신 객체 지정 람다이다.

     

    쓰임새

    Non-Nullable 수신 객체이고, 결과가 필요하지 않은 경우에 사용하라고 코틀린은 안내하고 있다.

     

    사용 예시

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

     

    also

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

     

    also의 정의를 살펴보면 위와 같다.

    with와의 차이점은 with는 수신 객체가 매개변수 T로 제공된다는 것인데, 이를 명시적으로 제공된 수신 객체라고 한다.

    also는 T의 확장함수로 수신객체가 암시적으로 전달되는 것을 알 수 있다.

     

    쓰임새

    수신 객체 지정 람다가 전달된 수신 객체를 전혀 사용하지 않거나,

    수신 객체의 속성을 변경하지 않고 사용하는 경우에 사용하라고 안내하고 있다.

     

    주로 객체의 사이드 이펙트를 확인하거나,

    수신 객체의 프로퍼티에 데이터를 할당하기 전 해당 데이터의 유효성을 검사할 때 사용된다고 한다.

     

    사용 예시

    with와 쓰이는 방식이 다른 것을 확인할 수 있다.

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

     

    let

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

     

    let 의 정의는 위와 같다.

     

    쓰임새

    - 지정된 값이 null이 아닌 경우에 코드를 실행해야하는 경우

    - nullable 객체를 다른 nullable로 변환해야하는 경우

    - 단일 지역 변수의 범위를 제한하는 경우

     

    사용 예시

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

     

    apply

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

     

    쓰임새

    수신 객체 지정 람다 내부에서 수신 객체의 함수를 사용하지 않고 수신 객체 자신을 다시 반환하려는 경우

    apply의 블록에서는 오직 프로퍼티만 사용할 것을 권장하고 있다.

     

    사용 예시

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

     

    run

    run은 사용 방식으로 분류했을 때 with 처럼도 쓸 수 있고 let 처럼도 사용할 수 있다.

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

     

    쓰임새

    - 어떤 값을 계산할 필요가 있거나 여러 개의 지역 변수의 범위를 제한하는 경우

    - 매개변수로 전달된 명시적 수신 객체를 암시적 수신 객체로 변환하는 경우

     

    사용 예시

    비확장 실행을 사용하면(with 처럼 사용하는 경우) 표현식이 필요한 여러 명령문 블록을 실행할 수 있다.

    코드에서 비확장 실행은 "코드 블록을 실행하고 결과를 계산합니다."로 읽을 수 있다.

    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)
    }
    
    // +123
    // -FFFF
    // 88

     

    let 과 같이 확장 실행을 사용하는 예시는 아래와 같다.

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

    공부하며 작성된 글이라 잘못된 정보가 있을 수 있습니다.

    말씀해주시면 수정하겠습니다. 감사합니다.

    References

    아래 글을 참고하여 작성 되었습니다.

     

    코틀린 의 apply, with, let, also, run 은 언제 사용하는가?

    원문 : “Kotlin Scoping Functions apply vs. with, let, also, and run”

    medium.com

     

     

    Scope functions | Kotlin

     

    kotlinlang.org

     

    범위 지정 함수와 수신 객체에 대하여

    범위 지정 함수의 정의는 코틀린 공식 문서에서 볼 수 있다. The Kotlin standard library contains several functions whose sole purpose is to execute a block of code within the context of an object. When you call such a function on an obj

    learnrecord.tistory.com

     

    728x90
    반응형

    'Android' 카테고리의 다른 글

    GPS 보정하기 - 03  (0) 2023.11.22
    GPS 보정하기 - 02  (0) 2023.11.20
    GPS 보정하기 - 01  (0) 2023.11.02
    GPS 보정하기 - 프롤로그  (0) 2023.11.01
    [Android] Hilt와 Room  (0) 2023.09.29
    댓글