Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Context Oriented Programming of Kotlin

Avatar for Yuki Toyoda Yuki Toyoda
September 19, 2025
4.2k

Context Oriented Programming of Kotlin

Avatar for Yuki Toyoda

Yuki Toyoda

September 19, 2025
Tweet

More Decks by Yuki Toyoda

Transcript

  1. Kotlinの高階関数はコンテクストを作る 高階関数はブロックとして記述することができる。これが「コンテクスト」を作る。 class DatabaseContext { fun startTransaction(block: (Transaction) -> Unit):

    Unit = TODO() } class Transaction fun callScopeFunctions() { // applyはKotlinのスコープ関数と呼ばれる関数のひとつ // fun <T> T.apply(block: T.() -> Unit): T DatabaseContext().apply { // DatabaseContextのコンテクスト startTransaction { tx -> // Transactionのコンテクスト } } } 6
  2. 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
  3. CoroutineScope coroutineScope 関数はブロックとしてCoroutineScopeのレシーバ付き関数リテラルを受け 取る。 async 関数は CoroutineScope の拡張関数になっている。 // coroutineScope関数

    suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R // async関数 fun <T> CoroutineScope.async( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T ): Deferred<T> 9
  4. 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
  5. Context ParametersによるCOPの拡張 関数に context(ctx: Context) を追加すると、裏で自動でコンテクストを伝播してくれる。 一方で、コンテクストがきちんと切り出されていない箇所で呼び出されるとコンパイルエラー で検知される。 context(ctx: Context)

    fun delegateContext() = ctx.call() fun main() { with(Context) { // これは問題ない delegateContext() } // コンテクストの切り出されていない箇所で呼び出すとコンパイルエラーになる delegateContext() } 12
  6. 型つきエラーハンドリング(Raise DSL) // 今回のコンテクスト interface Raise<E : Exception> { //

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

    : AppError() } context(raiseCtxt: Raise<AppError>) 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