Slide 1

Slide 1 text

Kotlinの「コンテクスト指向プログラミング (Context Oriented Programming)」 2025/09/19 Nextbeat Tech Bar:第七回関数型プログラミング(仮)の会

Slide 2

Slide 2 text

目次 自己紹介 Kotlinにおける高階関数と拡張関数 Kotlinの高階関数はコンテクストを作る Context Oriented Programming (COP) Context ParametersによるCOPの拡張 関数型プログラミングからの影響 まとめ 1

Slide 3

Slide 3 text

自己紹介 yuki (X: @helloyuki_) 所属: ソフトウェアアーキテクト @ Sansan株式会社 rust-lang/rustのコントリビュータやRust.Tokyoなど 以前はScalaエンジニアで、今はKotlinエンジニアをしている 2

Slide 4

Slide 4 text

免責事項 5分で細かく解説はできません!ごめんなさい。 代わりに細かい解説を含む記事を書きました。この資料を手がかりにしつつも、 気になる点があれば記事をご覧ください。 Kotlinの「コンテクスト指向プログラミング」とは何か?: https://blog- dry.com/entry/2025/09/19/132623 3

Slide 5

Slide 5 text

Kotlinにおける高階関数と拡張関数 Kotlinには高階関数がある。 fun foo(action: Actinable -> Unit): Unit 4

Slide 6

Slide 6 text

Kotlinにおける高階関数と拡張関数 また、拡張関数もある。特定の型に対して実装をあとから継ぎ足しできるイメージの機能。 < レシーバ>.関数名() で実装する。 // 拡張関数 fun Extendable.foo(): Unit // メンバー内拡張関数 interface SomeScope { fun Extendable.foo(): Unit } 5

Slide 7

Slide 7 text

Kotlinの高階関数はコンテクストを作る 高階関数はブロックとして記述することができる。これが「コンテクスト」を作る。 class DatabaseContext { fun startTransaction(block: (Transaction) -> Unit): Unit = TODO() } class Transaction fun callScopeFunctions() { // applyはKotlinのスコープ関数と呼ばれる関数のひとつ // fun T.apply(block: T.() -> Unit): T DatabaseContext().apply { // DatabaseContextのコンテクスト startTransaction { tx -> // Transactionのコンテクスト } } } 6

Slide 8

Slide 8 text

Context Oriented Programming 高階関数の作るコンテクストと拡張関数とを組み合わせると、コンテクストに応 じて利用できる関数が切り替わる実装をできる。 KotlinではこれをContext Oriented Programmingと呼ぶ。 An introduction to context-oriented programming in Kotlin: https://proandroiddev.com/an- introduction-context-oriented-programming-in-kotlin-2e79d316b0a2 Kotlinのコルーチンはこれを使ってよくデザインされた代表例だと思う。 7

Slide 9

Slide 9 text

CoroutineScope kotlinx.coroutinesというライブラリに入っている機能。 coroutineScope という関数が CoroutineScope のコンテクストを生成し、その中 であればたとえば async などの関数を呼び出せる。 この実装にはまさに、高階関数と拡張関数が利用されている。 8

Slide 10

Slide 10 text

CoroutineScope coroutineScope 関数はブロックとしてCoroutineScopeのレシーバ付き関数リテラルを受け 取る。 async 関数は CoroutineScope の拡張関数になっている。 // coroutineScope関数 suspend fun coroutineScope(block: suspend CoroutineScope.() -> R): R // async関数 fun CoroutineScope.async( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T ): Deferred 9

Slide 11

Slide 11 text

CoroutineScope これらを組み合わせると、 coroutineScope のコンテクスト内で async 関数を呼び出せる。 import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope suspend fun callCoroutine() { coroutineScope { // async関数はCoroutinesScopeのコンテクスト内でのみ呼び出せる。 val firstTask = async { highLoadFunction() } val secondTask = async { highLoadFunction() } // Continues... } } 10

Slide 12

Slide 12 text

Context ParametersによるCOPの拡張 さらにKotlin 2.2.0になって、コンテクスト情報を伝播させられるContext Parametersという実験的な機能が実装された。 Scalaのimplicitsによく似ている。 11

Slide 13

Slide 13 text

Context ParametersによるCOPの拡張 関数に context(ctx: Context) を追加すると、裏で自動でコンテクストを伝播してくれる。 一方で、コンテクストがきちんと切り出されていない箇所で呼び出されるとコンパイルエラー で検知される。 context(ctx: Context) fun delegateContext() = ctx.call() fun main() { with(Context) { // これは問題ない delegateContext() } // コンテクストの切り出されていない箇所で呼び出すとコンパイルエラーになる delegateContext() } 12

Slide 14

Slide 14 text

Context ParametersによるCOPの拡張 これを利用すると、たとえば下記を実装できる。 いわゆるモノイド。 型つきエラーハンドリング(Raise DSL)。 Raise DSLを今回は紹介する。 13

Slide 15

Slide 15 text

型つきエラーハンドリング(Raise DSL) // 今回のコンテクスト interface Raise { // 今回はエラーを単に送出するだけにする fun raise(error: E): Nothing = throw error } // AppError用のコンテクストをハンドリングする関数を定義する。 inline fun handle(block: context(Raise) () -> T): T { return try { val raiseImpl = object : Raise {} with(raiseImpl) { block() } } catch (e: Exception) { // 何かしらのハンドリングをする。今回は便宜のために単に呼び出し元に伝播する。 throw e } } 14

Slide 16

Slide 16 text

型つきエラーハンドリング(Raise DSL) sealed class AppError : Exception() { object NegativeNumber : AppError() } context(raiseCtxt: Raise) fun validateNumber(x: Int): Int { if (x < 0) { raiseCtxt.raise(AppError.NegativeNumber) } else { return x } } fun callRaiseDSL() { // こちらは通常のバリデーションが通り、値が返ってくる。 val result = handle { validateNumber(1) } // バリデーションの結果AppError.NegativeNumberが送出される。 val error = handle { validateNumber(-1) } } 15

Slide 17

Slide 17 text

関数型プログラミングからの影響 Context ParametersのDesign Docでも言及があるが、いわゆる型クラスに近い概 念と言える? また、コンパイル時にコンテクストが十分かどうかを検査することから、 Coeffects的だと言える。 Raise DSLはAlgebraic Effectに似ている。 など。 16

Slide 18

Slide 18 text

まとめ KotlinにはContext Oriented Programmingという考え方がある。 これは結構関数型プログラミングで議論されるコンセプトの影響を受けている。 17

Slide 19

Slide 19 text

参考文献 KEEP-0259: https://github.com/Kotlin/KEEP/blob/main/proposals/KEEP-0259- context-receivers.md KEEP-0367: https://github.com/Kotlin/KEEP/blob/main/proposals/KEEP-0367- context-parameters.md Functional Error Handling in Kotlin: Part 3 - The Raise DSL: https://rockthejvm.com/articles/functional-error-handling-in-kotlin-part-3-the-raise- dsl 18