Context Parameterから見る関数の暗黙的な依存について

>100 Views

July 03, 26

スライド概要

Server-Side Kotlin LT大会 vol.19 での発表資料です

profile-image

1日が30時間くらいほしいです

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

Context Parameterから見る 関数の暗黙的な依存について @Udomomo 2026-07-03 Server-Side Kotlin LT大会 vol.19

2.

プロフィール • @Udomomo • バックエンドエンジニアとしてKotlinで サービスの開発をしています • 最近はオライリーの「コンピュータシステム の理論と実装」にハマっています

3.

背景 • Kotlin 2.4で正式リリースとなったContext Parameter • どういう機能かはドキュメントを見てなんとなくわかった • でもそもそもコンテキストって…? (うまく言葉にできない)

4.

コンテキストにつながる概念 「Dispatch Receiver」

5.
[beta]
例: 従業員の情報を出力する拡張関数
EmployeeReportRenderer の中で定義した Employee の拡張関数には、
EmployeeReportRenderer が存在するスコープ配下でのみアクセス可能。
class EmployeeReportRenderer(
private val showPersonalInfo: Boolean,
) {
fun Employee.toReportLine(): String =
buildString {
append("$id: ")
append(department)
if (showPersonalInfo) {
append(" $name<$email>")
}
}
fun render(employees: List<Employee>): String =
employees.joinToString("\n") {
it.toReportLine()
}
}

// ✅OK
employeeReportRenderer.render(
listOf(employee)
)
// ✅OK
with(employeeReportRenderer) {
println(employee.toReportLine())
}
// ❌NG
employee.toReportLine()

6.

拡張関数とDispatch Receiver あるクラスAの拡張関数が別のクラスB内で定義された時、Bのインスタンスは Dispatch Receiverとなり、拡張関数内で明示されずに利用される。 ・拡張関数の直接の対象。先ほどの例では Employee がExtension Receiver。 Extension Receiver ・Employee.toReportLine() や it.toReportLine() のように、レシーバを明示的 に指定して呼んでおり、明示的レシーバの一種といえる。 • 拡張関数が定義されたクラスのインスタンス。先ほどの例では EmployeeReportRenderer がDispatchReceiver。 Dispatch Receiver • toReportLine() の中では、 EmployeeReportRenderer のプロパティである showPersonalInfo を使っているが、レシーバを明示せずに呼んでいる。これは 暗黙的レシーバの一種であり、コンテキストの概念につながる。

7.

暗黙的な依存としてのコンテキスト 多くの関数の処理結果に関わるが、直接の処理対象ではない関心事というのが しばしば存在する。(ロギング、ユーザ解決、権限確認など…) これらをコンテキスト(文脈) と呼び、関数の暗黙的な依存とみなすことも できる。 Dispatch Receiverも関数から暗黙的に利用されているため、コンテキストに 近い性質があるが、あくまで特定のクラスのメンバとしてしか定義ができず、 柔軟に扱うのには限界がある。

8.

Context Receiver

9.

Context Receiver • Kotlin 1.6.20でプロトタイプとして導入 • 関数に context(…) を付与することで、指定した型が関数の暗黙的な依存と して振る舞う • 指定したcontextは、内側のスコープにも引き継がれていく • そのため、interface型を関数のcontextに指定し、さらに外側で指定した contextによって解決させることも可能

10.

Context Receiverの例 Contextとして渡す用のinterface。先ほどの例でEmployeeReportRenderer の中にあったshowPersonalInfoを、今度はこの中で定義する。 interface EmployeeReportPolicy { val showPersonalInfo: Boolean } class ExecutivePersonalInfoPolicy ( override val showPersonalInfo: Boolean, ): EmployeeReportPolicy

11.
[beta]
Context Receiverの例
showPersonalInfo をcontextとして外部から渡すことが可能になった。
class EmployeeReportRenderer {
context(EmployeeReportPolicy)
fun Employee.toReportLine(): String =
buildString {
append("$id: ")
append(department)
if (showPersonalInfo) {
append(" $name<$email>")
}
}
context(EmployeeReportPolicy)
fun render(employees: List<Employee>): String =
employees.joinToString("\n") {
it.toReportLine()
}
}

12.

Context Receiverの例 Contextとして渡されたinterfaceの実装は、スコープを上って解決される。 val employee = Employee(“001”, “[email protected]”, “name”, “department”) val employeeReportRenderer = EmployeeReportRenderer() val executivePersonalInfoPolicy = ExecutivePersonalInfoPolicy(showPersonalInfo = false) with(executivePersonalInfoPolicy) { println(employeeReportRenderer.render(listOf(employee))) }

13.

Context Receiverの問題と Context Parameter

14.
[beta]
Context Receiverの問題点: スコープ汚染
• Contextを考慮しなければ、スコープの内外関係 = ネストの内外関係
• しかしcontextがこの位置にある以上、ネストの順番とスコープが等しくなく
なる

• スコープを登って行く間、最初はContextは無視され、最後に考慮される
class A {
context(B1, B2)
fun C.f() {
// A group: [B1, B2]
with (d) { // Add receiver to the scope
// Resolution order: d -> C -> A -> [B1, B2] -> imports
}
}
}
https://github.com/Kotlin/KEEP/blob/master/proposals/context-receivers.md#resolution-algorithm

15.

Context Receiverの問題点: スコープ汚染 • Context Receiverがない状態でさえ、1つのネストレベルに1個のImplicit Receiverが存在しうる • ここにContext Receiverが加わると、全ネストレベルのImplicit Receiverの 数が無制限になり、定義元のスコープの解決が困難になりうる class AClass { // this: AClass fun Extension.doSomething() { // this: Extension dsl1 { // this: Builder1 dsl2 { // this: Builder2 foo() // where is this function declared? } } } } https://github.com/Kotlin/KEEP/blob/master/proposals/context-receivers.md#context-receivers-abuse-and-scope-pollution

16.

Context Parameter • Kotlin 2.4.0でStableリリース (※ 一部の付随機能はExperimental) • 暗黙のスコープの利用を抑える設計に変更 • 関数内から参照させたい場合はパラメータ名が必須 • Context Parameterの付与を関数・プロパティ単位に限定

17.
[beta]
Context Parameterの例
Contextにパラメータ名がつき、関数から明示的にアクセスするようになった。
class EmployeeReportRenderer {
context(policy: EmployeeReportPolicy)
fun Employee.toReportLine(): String =
buildString {
append("$id: ")
append(department)
if (policy.showPersonalInfo) {
append(" $name<$email>")
}
}
context(policy: EmployeeReportPolicy)
fun render(employees: List<Employee>): String =
employees.joinToString("\n") {
it.toReportLine()
}
}

18.

Context Parameterの例 ただし呼び出し側は変わっていない。ExecutivePersonalInfoPolicy を 引数として渡しているわけではなく、contextの解決は暗黙的に行われる。 val employee = Employee(“001”, “[email protected]”, “name”, “department”) val employeeReportRenderer = EmployeeReportRenderer() val executivePersonalInfoPolicy = ExecutivePersonalInfoPolicy(showPersonalInfo = false) with(executivePersonalInfoPolicy) { println(employeeReportRenderer.render(listOf(employee))) }

19.

まとめ • 引数に現れない暗黙的な参照 (特にDispatch Receiver) を、コンテキストと いう概念として扱えるようにする試みがあった • Context Receiverは、コンテキストを暗黙的なレシーバとして関数から 要求可能にする仕組みだった • ただ定義元のスコープ解決が過度に複雑になったため、Context Parameter では名前つきのパラメータとして扱う形に再設計された • その結果、暗黙的なコンテキストでありつつも、関数内からは明示的に アクセスできるという、複合的な性質を持つようになった