문맥 매개변수

문맥 매개변수 (Context parameters)

문맥 매개변수(context parameters)는 문맥 리시버(context receivers)라는 더 오래된 실험적 기능을 대체해요. 둘의 주요 차이점은 문맥 매개변수 설계 문서에서 찾을 수 있습니다. 문맥 리시버에서 문맥 매개변수로 마이그레이션하려면 관련 블로그 글에서 설명하는 IntelliJ IDEA의 지원 기능을 사용할 수 있어요.

출처: Kotlin 공식 문서 — Context parameters

본문

문맥 매개변수를 사용하면 함수와 속성이, 주변 문맥에서 암시적으로 사용할 수 있는 의존성을 선언할 수 있습니다.

문맥 매개변수를 쓰면 서비스나 의존성처럼 여러 함수 호출 집합에 걸쳐 공유되고 거의 바뀌지 않는 값을 일일이 전달할 필요가 없어요.

속성과 함수에 문맥 매개변수를 선언하려면 context 키워드 뒤에 매개변수 목록을 쓰고, 각 매개변수를 name: Type으로 선언하면 됩니다. 다음은 UserService 인터페이스에 의존하는 예시예요.

// UserService defines the dependency required in context 
interface UserService {
    fun log(message: String)
    fun findUserById(id: Int): String
}

// Declares a function with a context parameter
context(users: UserService)
fun outputMessage(message: String) {
    // Uses log from the context
    users.log("Log: $message")
}

// Declares a property with a context parameter
context(users: UserService)
val firstUser: String
    // Uses findUserById from the context    
    get() = users.findUserById(1)

fun main() {
    val users = object : UserService {
        override fun log(message: String) {
            println(message)
        }

        override fun findUserById(id: Int): String {
            return "User $id"
        }
    }

    context(users) {
        outputMessage("Looking up the first user")
        println(firstUser)
        // User 1
    }
}

매개변수를 직접 참조할 필요가 없을 때는 문맥 매개변수 이름으로 _를 쓸 수 있어요. 익명 문맥 매개변수는 호출되는 함수가 요구하는 문맥 매개변수를 충족시킬 수 있지만, 이름으로 접근할 수는 없습니다. 값을 명시적으로 접근하려면 contextOf<T>()를 사용하세요.

// Uses "_" as context parameter name
context(_: UserService)
fun logWelcome() {
    // The anonymous parameter satisfies the UserService context parameter
    // required by outputMessage()
    outputMessage("Welcome!")

    // Retrieves the UserService value explicitly
    contextOf<UserService>().log("Hi!")
}

문맥 매개변수 해석 (Context parameters resolution)

코틀린은 호출 지점에서 현재 스코프의 일치하는 문맥 값을 검색해서 문맥 매개변수를 해석합니다. 타입으로 일치시키죠. 같은 스코프 수준에 호환되는 값이 여러 개 있으면 컴파일러가 모호성(ambiguity)을 보고합니다.

// UserService defines the dependency required in context
interface UserService {
    fun log(message: String)
}

// Declares a function with a context parameter
context(users: UserService)
fun outputMessage(message: String) {
    users.log("Log: $message")
}

fun main() {
    // Implements UserService 
    val serviceA = object : UserService {
        override fun log(message: String) = println("A: $message")
    }

    // Implements UserService
    val serviceB = object : UserService {
        override fun log(message: String) = println("B: $message")
    }

    // Both serviceA and serviceB match the expected UserService type at the call site
    context(serviceA, serviceB) {
        // This results in an ambiguity error
        outputMessage("This will not compile")
    }
}

문맥 인자를 명시적으로 전달하기

오버로드가 문맥 매개변수로만 구분될 때, 일치하는 문맥 값이 여러 개 있으면 호출이 모호해질 수 있어요.

모호성을 해결하려면 호출 지점에서 명시적인 문맥 인자를 전달하세요.

class EmailSender
class SmsSender

context(emailSender: EmailSender)
fun sendNotification() {
    println("Sent email notification")
}

context(smsSender: SmsSender)
fun sendNotification() {
    println("Sent SMS notification")
}

context(defaultEmailSender: EmailSender, defaultSmsSender: SmsSender)
fun notifyUser() {
    // Selects the overload with the EmailSender context parameter
    sendNotification(emailSender = defaultEmailSender)

    // Selects the overload with the SmsSender context parameter
    sendNotification(smsSender = defaultSmsSender)
}

명시적 문맥 인자를 사용해 일부 함수 호출의 중첩을 줄일 수도 있어요.

  • 단일 호출이면 명시적 문맥 인자를 사용해서 호출을 더 읽기 쉽게 만들 수 있습니다.
  • 여러 호출이 같은 문맥 인자를 쓴다면 context() 함수를 사용하세요.

이 기능은 Experimental입니다. 사용하려면 빌드 파일에 다음 컴파일러 옵션을 추가하세요.

kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xexplicit-context-arguments")
    }
}
<build>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <configuration>
                <args>
                    <arg>-Xexplicit-context-arguments</arg>
                </args>
            </configuration>
        </plugin>
    </plugins>
</build>

제약 사항 (Restrictions)

문맥 매개변수는 계속 개선 중이며, 현재 다음과 같은 제약이 있습니다.

  • 생성자는 문맥 매개변수를 선언할 수 없습니다.
  • 문맥 매개변수가 있는 속성은 지원 필드(backing field)나 초기화를 가질 수 없어요.
  • 문맥 매개변수가 있는 속성은 위임(delegation)을 사용할 수 없습니다.

이런 제약에도 불구하고, 문맥 매개변수는 단순화된 의존성 주입, 개선된 DSL 설계, 범위 지정 연산(scoped operations)을 통해 의존성 관리를 단순화합니다.

더 알아보기 (Learn more)