문맥 매개변수
문맥 매개변수 (Context parameters)
문맥 매개변수(context parameters)는 문맥 리시버(context receivers)라는 더 오래된 실험적 기능을 대체해요. 둘의 주요 차이점은 문맥 매개변수 설계 문서에서 찾을 수 있습니다. 문맥 리시버에서 문맥 매개변수로 마이그레이션하려면 관련 블로그 글에서 설명하는 IntelliJ IDEA의 지원 기능을 사용할 수 있어요.
본문
문맥 매개변수를 사용하면 함수와 속성이, 주변 문맥에서 암시적으로 사용할 수 있는 의존성을 선언할 수 있습니다.
문맥 매개변수를 쓰면 서비스나 의존성처럼 여러 함수 호출 집합에 걸쳐 공유되고 거의 바뀌지 않는 값을 일일이 전달할 필요가 없어요.
속성과 함수에 문맥 매개변수를 선언하려면 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)을 통해 의존성 관리를 단순화합니다.