コンテキストパラメータ
コンテキストパラメータ(Context parameters)は、コンテキストレシーバーと呼ばれていた古い試験的機能を置き換えるものです。 主な違いについては、コンテキストパラメータのデザインドキュメント(英語)で確認できます。 コンテキストレシーバーからコンテキストパラメータへの移行については、関連するブログ記事で説明されているように、IntelliJ IDEAのアシスト機能を使用できます。
コンテキストパラメータを使用すると、関数やプロパティが、周囲のコンテキストで暗黙的に利用可能な依存関係を宣言できるようになります。
コンテキストパラメータを使用すれば、一連の関数呼び出しの間で共有され、めったに変更されないサービスや依存関係などの値を、手動で渡し続ける必要がなくなります。
プロパティや関数に対してコンテキストパラメータを宣言するには、context キーワードに続けて、name: Type 形式で宣言されたパラメータのリストを使用します。以下は、UserService インターフェースへの依存関係を持つ例です。
// UserService はコンテキストで必要とされる依存関係を定義します
interface UserService {
fun log(message: String)
fun findUserById(id: Int): String
}
// コンテキストパラメータを持つ関数を宣言します
context(users: UserService)
fun outputMessage(message: String) {
// コンテキストの users から log を使用します
users.log("Log: $message")
}
// コンテキストパラメータを持つプロパティを宣言します
context(users: UserService)
val firstUser: String
// コンテキストの users から findUserById を使用します
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
}
}コンテキストパラメータ名として _ を使用することもできます。この場合、パラメータの値は解決(resolution)には利用可能ですが、ブロック内で名前を使ってアクセスすることはできません。
// コンテキストパラメータ名として "_" を使用します
context(_: UserService)
fun logWelcome() {
// 解決プロセスにより、UserService から適切な log 関数が引き続き見つけられます
outputMessage("Welcome!")
}コンテキストパラメータの解決
Kotlinは、現在のスコープ内で一致するコンテキスト値を検索することで、呼び出し側(call site)でコンテキストパラメータを解決します。Kotlinはそれらを型によって照合します。 同じスコープ階層に複数の互換性のある値が存在する場合、コンパイラは曖昧さ(ambiguity)エラーを報告します。
// UserService はコンテキストで必要とされる依存関係を定義します
interface UserService {
fun log(message: String)
}
// コンテキストパラメータを持つ関数を宣言します
context(users: UserService)
fun outputMessage(message: String) {
users.log("Log: $message")
}
fun main() {
// UserService を実装します
val serviceA = object : UserService {
override fun log(message: String) = println("A: $message")
}
// UserService を実装します
val serviceB = object : UserService {
override fun log(message: String) = println("B: $message")
}
// 呼び出し側で、serviceA と serviceB の両方が期待される UserService 型に一致します
context(serviceA, serviceB) {
// これは曖昧さエラーになります
outputMessage("This will not compile")
}
}コンテキスト引数を明示的に渡す
オーバーロードがコンテキストパラメータのみで異なる場合、複数の一致するコンテキスト値が利用可能であれば、呼び出しが曖昧(ambiguous)になる可能性があります。
この曖昧さを解決するには、呼び出し側で明示的なコンテキスト引数(explicit context argument)を渡します:
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() {
// EmailSender コンテキストパラメータを持つオーバーロードを選択します
sendNotification(emailSender = defaultEmailSender)
// SmsSender コンテキストパラメータを持つオーバーロードを選択します
sendNotification(smsSender = defaultSmsSender)
}また、一部の関数呼び出しでネストを減らすために、明示的なコンテキスト引数を使用することもできます:
- 単一の呼び出しの場合、明示的なコンテキスト引数を使用して、呼び出しを読みやすくします。
- 複数の呼び出しで同じコンテキスト引数を使用する場合は、
context()関数を使用します。
この機能は試験的です。オプトインするには、ビルドファイルに以下のコンパイラオプションを追加してください:
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>制限事項
コンテキストパラメータは継続的に改善が行われており、現在の制限事項には以下のものが含まれます:
- コンストラクタにはコンテキストパラメータを宣言できません。
- コンテキストパラメータを持つプロパティは、バッキングフィールド(backing field)や初期化子(initializer)を持つことができません。
- コンテキストパラメータを持つプロパティは、委譲(delegation)を使用できません。
これらの制限はありますが、コンテキストパラメータは依存関係注入の簡略化、DSL設計の向上、およびスコープ限定の操作を通じて、依存関係の管理を簡素化します。
