kotlin 수신 객체 지정 람다 - with

조갱·2022년 7월 10일
2

kotlin

목록 보기
7/10

*[이곳 - kotlin 람다와 멤버참조]과 관련이 있는 게시물입니다!

여러 언어에서 with문을 제공하고 있다. (Java는 없다..)
어릴 적 Visual Basic 6.0을 사용해본 경험이 있는데, kotlin 의 with가 VB 6.0의 with문과 쓰임새가 유사하다.
(C#은 9.0부터 with문을 제공하긴 하는데, kotlin과 사용 목적이 조금 차이가 있어보인다.)

지금까지 with는 with(변수) {...}로 사용하는것을 당연하게 생각하고 있었는데, 어떻게 저렇게 동작하는지 (나만 몰랐던)새롭게 알게 된 사실을 소소하게 공유하고자 한다.!

언제 쓰는지?

우선, with에 대해서 생소한 사람도 있을 것 같아, with가 뭐하는 애인지부터 소개해본다.

값을 여러번 중복하여 사용할 때 가독성을 높일 수 있다.

예시를 위해 조금 뻘짓해보자.
StringBuilder 에 a 부터 g까지 넣어서 abcd 를 만드는makeABCD() 메소드이다.

fun makeABCD(): String{
    val stringBuilder = StringBuilder()
    stringBuilder.append('a')
    stringBuilder.append('b')
    stringBuilder.append('c')
    stringBuilder.append('d')
    return stringBuilder.toString()
}

전혀 문제되는 코드는 아니지만 보기가 굉장히 불편하다.

fun makeABCD() : String{
    val stringBuilder = StringBuilder()
    with(stringBuilder) { // this -> StringBuilder
        this.append('a')
        this.append('b')
        this.append('c')
        this.append('d')
    }
    return stringBuilder.toString()
}

with를 쓰면 위와 같이 사용할 수 있다.
참고로, this는 생략할 수 있으므로, 아래와 같이 쓸 수도 있다.

fun makeABCD() : String{
    val stringBuilder = StringBuilder()
    with(stringBuilder) { // this -> StringBuilder
        append('a')
        append('b')
        append('c')
        append('d')
    }
    return stringBuilder.toString()
}

with문은 인자로 받은 객체를 그대로 반환하기 때문에, 아래와 같이 쓸 수도 있다.

fun makeABCD() : String{
    return with(StringBuilder()) { // this -> StringBuilder
        append('a')
        append('b')
        append('c')
        append('d')
    }.toString()
}

또는

fun makeABCD() : String{
    return with(StringBuilder()) { // this -> StringBuilder
        append('a')
        append('b')
        append('c')
        append('d')
        toString()
    }
}

동작 원리

동작 원리를 파악하기 위해 with 문의 정의를 살펴보자.

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

이전 포스팅 [kotlin 람다와 멤버참조] 에서, 파라미터의 맨 마지막에 람다를 받으면 바깥으로 뺄 수 있다는 내용을 언급한 적이 있다.

with도 위와 같은 원리로 동작하던 것이다. (몰랐던 사실,,)
그래서 위 makeABCD() 메소드를 다시 확인하면,

fun makeABCD() : String{
   return with(receiver: T) {
       block: T.() -> R
   }.toString() // block에서 append() 의 반환값은 StringBuilder이기 때문에 (StringBuilder).toString() 과 같음

이 된다.

참고로, callsInPlace는 kotlin 1.3부터 도입되었는데, 람다가 해당 위치에서 실행되며, (선택적으로) 실행되는 횟수를 컴파일러에게 전달하기 위해 사용한다고 한다.
이를 통해 1.2까지 (안전성을 위해) 컴파일되지 않던 일부 코드들이 컴파일러에게 실행됨을 명시하며 컴파일이 가능하게 되었다.

Reference
Visual Basic 6.0 - with : https://docs.microsoft.com/ko-kr/dotnet/visual-basic/language-reference/statements/with-end-with-statement
C# 9.0 - with : https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/operators/with-expression
Kotlin in Action (책)
callsInPlace : https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.contracts/-contract-builder/calls-in-place.html
https://stackoverflow.com/questions/65988413/which-are-the-benefits-of-kotlin-callsinplace-contract

profile
A fast learner.

0개의 댓글