Kotlin Scope functions

Bettor·2024년 4월 4일
0

Kotlin_Functions

목록 보기
1/1

Scope function에는 let, run, with, apply, also 라는 함수가 있다고 합니다. 일단은 scope는 범위라는 뜻인데,

  1. 람다식을 이용해서 호출하면 일시적인 범위가 생기게 되고, 이 범위 안에서는 전달된 객체에 대해 "it" 또는 "this"라는 Context object를 통해서 access하게 된다고 합니다.
  1. 객체에서 코드 블록을 실행하는데, 함수들은 모두 동일한 작업을 수행하지만, 다른 점은 이 객체가 블록 내에서 사용 가능해지는 방식과 전체 표현식의 결과가 무엇인지이다.

무슨 말인지 알아들을 수 없으니 예제로 알아보자.

Person("Alice", 20, "Amsterdam").let {
    println(it)
    it.moveTo("London")
    it.incrementAge()
    println(it)
}

val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)

위의 두 코드를 비교해보자.
아래 코드는 변수를 사용하고자 할때마다 계속 선언 해줘야 하는데, 위의 코드는 Alice를 여러번 부르지 않고 let 함수를 통하여 it으로 간소화한 것을 알 수 있다.

공식 문서에는 이렇게 설명하고 있다.

Due to the many similarities between scope functions, choosing the right one for your use case can be tricky. The choice mainly depends on your intent and the consistency of use in your project. Below, we provide detailed descriptions of the differences between scope functions and their conventions.

걍 적재적소에 쓰라는 뜻이다 ㅋㅋ..

let으로 예를 들었고 공식 문서에도 기능 간의 주요 차이점을 요약한 표를 제공하고 있었다.

Distinctions

범위 기능은 본질적으로 유사하기 때문에 이들 간의 차이점을 이해하는 것이 중요하다. 각 범위 함수에는 두 가지 주요 차이점이 있다.

1. context object를 참조하는 방식

2. 반환 값

context object: this or it

위에서도 설명했지만 this나 it으로 짧은 참조를 할 수 있다는데 목적에 따라 하나를 선택해서 사용해야 한다.

this

run(), with(), apply()는 this를 사용하여 컨텍스트 개체를 람다 수신기로 참조한다. 따라서 람다에서는 일반 클래스 함수에서와 마찬가지로 객체를 사용할 수 있습니다.

대부분의 경우 수신자 객체의 멤버에 액세스할 때 이를 생략하여 코드를 더 짧게 만들 수 있습니다. 반면, 이를 생략하면 수신자 멤버와 외부 객체나 기능을 구분하기 어려울 수 있다. 따라서 함수를 호출하거나 속성에 값을 할당하여 객체의 멤버에 대해 주로 작동하는 람다에는 컨텍스트 객체를 수신기(this)로 갖는 것이 좋다.

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

여러 예제들을 보아도, 위의 주석을 보아도 this는 생략 되는 경우들이 많은 것 같다. 그래서 첫 문단에도 일반 클래스 함수에서와 마찬가지로 객체로 사용 할 수 있다는 말이 그래서 그런 것 같다. 하지만 위의 코드처럼 age = 20으로 너무 생략해버리면 두번째 문단의 설명처럼 수신자 멤버와 외부 객체나 기능을 구분하기 어렵다.

it

let()과 also()는 컨테스트 객체를 람다 인수로 참조한다.
인수 이름을 지정하지 않으면 암시적 기본 이름인 it을 사용하여 객체에 액세스합니다.
it은 this보다 짧아 일반적으로 읽기 쉬운 표현이다.

그러나 객체의 함수나 속성을 호출할 때 이와 같이 암시적으로 객체를 사용할 수는 없습니다. 따라서 객체가 함수 호출에서 인수로 주로 사용될 때 이를 통해 컨텍스트 객체에 액세스하는 것이 더 좋습니다. 코드 블록에서 여러 변수를 사용하는 경우에도 더 좋습니다.

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

val i = getRandomInt()
println(i)

아래 문단은 뭔소린지 모르겠다.
그러나 예제로 알 수 있는 부분은 it을 객체로(?) 쓸 수는 없고, 인수로서 액세스(?) 할 수 있다는 것 같다. 여기서 it은 Random.nextInt(100)를 반환한 값을 말하는 것 같다.

인수 이름이 없어서 it, 그리고 인수이름이 있는 경우.

위에는 인수 이름이 없어서 it을 사용했지만 아래 코드는 인수 이름이 value인 사례이다.

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

val i = getRandomInt()
println(i)

자 여기까지는 context object를 참조하는 방식이였고, 이제는 반환 값에 대해 알아보겠다.

Return Value

범위 함수는 반환하는 결과에 따라 다르다.
1. apply와 also는 context object를 반환한다.
2. let, run, with은 람다 결과를 반환한다.

코드에서 다음에 수행할 작업에 따라 원하는 반환 값을 신중하게 고려해야 한다. 이는 사용할 최상의 범위 기능을 선택하는 데 도움이 된다.

Context object

apply와 also의 반환 값은 context object 그 자체이다.
따라서 두 함수는 call chains에 side steps로 포함 될 수 있다. 뭔소리지~

동일한 객체에 대해 함수 호출을 하나씩 계속해서 연결할 수 있습니다.

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

또한 컨텍스트 개체를 반환하는 함수의 반환 문에도 사용할 수 있다.

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

val i = getRandomInt()

갑자기 뻘한 당연한 소리지만 return할때도 쓸 수 있대요~

Lambda 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.")

: 변수를 잘 보시오!

또한 반환 값을 무시하고 범위 함수를 사용하여 지역 변수에 대한 임시 범위를 만들 수 있습니다.

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    val firstItem = first()
    val lastItem = last()        
    println("First item: $firstItem, last item: $lastItem")
}

여기까지 기본적인 Scope Functions에 대해 알아보았다.

일단은 반환 값 그 자체인게 context object인 것 같다. age라는 변수가 계속 변하는 것 같은?
근데 람다 결과라고 하긴 했지만 람다식은 context object처럼 객체를 계속 변하게 만드는게 아니라 변수를 이용해 새로운 변수로 결과를 만드는 방법인 것 같다.

아래 링크처럼 이제는 함수들의 기능에 대해 다뤄야하는데 나중에 다뤄보겠다.

https://kotlinlang.org/docs/scope-functions.html#functions

0개의 댓글