Kotlinは、Javaとの高い互換性を維持しながら、関数型プログラミング(Functional Programming, FP)の考え方を強力に取り入れた言語です。
ただし、KotlinはHaskellのような純関数型言語ではなく、
オブジェクト指向(OOP)を基盤としつつ、関数型スタイルを強く支援する言語
という立ち位置にあります。
本記事では、Kotlinにおける「関数型」の意味を正確な定義・実装仕様・実務での使い所まで含めて解説します。
目次
Kotlinにおける「関数型」とは何か
Kotlinの関数型プログラミングの特徴は以下に集約されます。
- 関数を値として扱える(第一級関数)
- 関数を引数・戻り値として扱える(高階関数)
- ラムダ式の構文が簡潔で実用的
- 不変データ(イミュータブル)を使いやすい
- 宣言的なコレクション操作が可能
- OOPと対立せず、補完的に使える
重要なのは、Kotlinは関数型を強制しないという点です。
命令型・OOP・関数型を状況に応じて使い分ける設計が前提になっています。
関数は「値」として扱える(第一級関数)
Kotlinでは、関数そのものを値として扱えます。
val add: (Int, Int) -> Int = { a, b -> a + b }
この (Int, Int) -> Int が 関数型(function type) です。
関数型の構造
(引数1の型, 引数2の型, ...) -> 戻り値の型
呼び出しは通常の関数と同じです。
add(2, 3) // 5
名前付き関数と関数参照
通常の関数も、関数型として扱えます。
fun multiply(a: Int, b: Int): Int = a * b
val calc: (Int, Int) -> Int = ::multiply
ここで使っている ::multiply は 関数参照(function reference) です。
補足
- 関数参照は「関数そのもの」ではなく、関数を指す参照値
- オーバーロードがある場合、型指定が必要になることがある
高階関数(関数を引数に取る関数)
関数型プログラミングの中核です。
fun calculate(
a: Int,
b: Int,
operation: (Int, Int) -> Int
): Int {
return operation(a, b)
}
使用例
calculate(10, 5) { x, y -> x - y }
トレーリングラムダ
- 最後の引数が関数の場合、括弧の外に書ける
- Kotlinの関数型が実用的である最大の理由の一つ
ラムダ式の構文と省略ルール
基本形
{ x: Int, y: Int -> x + y }
型推論
{ x, y -> x + y }
暗黙引数 it
list.map { it * 2 }
正確な条件
- 引数が1つ
- 引数名を明示的に書かない
list.map { x -> x * 2 } // itは使わない
コレクション操作と関数型スタイル
Kotlinの標準ライブラリは、関数型スタイルを強く支援しています。
val numbers = listOf(1, 2, 3, 4, 5)
val result = numbers
.filter { it % 2 == 1 }
.map { it * 10 }
.fold(0) { acc, value -> acc + value }
println(result) // 90
特徴
- 「どう処理するか」より「何をしたいか」が明確
- 状態を持たず、処理が直線的に読める
- テストしやすい
不変性(イミュータブル)と副作用
副作用が発生しやすい例
var sum = 0
numbers.forEach { sum += it }
- 問題は
forEachではなく、外部の可変状態を書き換えていること
副作用を排除した例
val sum = numbers.sum()
または
val sum = numbers.fold(0) { acc, value -> acc + value }
Kotlinでは「不変 + 関数合成」が安全な設計につながります。
純粋関数(Pure Function)
純粋関数の条件
- 同じ入力に対して常に同じ出力
- 外部状態を変更しない
fun add(a: Int, b: Int): Int = a + b
注意点
- Kotlinには「この関数は純粋」と保証する言語機構はない
- 設計・規約として純粋に保つのが前提
関数型インターフェース(SAM)
fun interface ClickListener {
fun onClick(x: Int)
}
val listener = ClickListener { x ->
println("Clicked: $x")
}
- JavaのSAMと完全互換
- Kotlinでは
fun interfaceにより明示的に定義可能
inline / noinline / crossinline
なぜ inline が必要か
- ラムダが外部変数をキャプチャすると、Functionオブジェクトが生成される
- 頻繁な呼び出しではオーバーヘッドになる
inline fun measure(block: () -> Unit) {
val start = System.currentTimeMillis()
block()
println(System.currentTimeMillis() - start)
}
非ローカルreturn
inline fun f(block: () -> Unit) {
block()
}
fun g() {
f { return } // g()からreturnできる
}
- 非ローカルreturnは inline ラムダでのみ可能
crossinline
- 非ローカルreturnを禁止する
- ラムダを別スレッド・別場所で実行する可能性がある場合に必須
Kotlinにおける関数型設計のメリット
- テスト容易性が高い
- 状態バグが減る
- 宣言的で読みやすい
- 並行処理と相性が良い
- UI・データ変換・ビジネスロジックに強い
実務での使い分け指針
| 領域 | 推奨 |
|---|---|
| データ変換 | 関数型 |
| 状態管理 | 不変 + 関数 |
| UIイベント | SAM + ラムダ |
| 高頻度処理 | inline併用 |
| 複雑な状態 | OOPと併用 |
次のステップ
Sequence:遅延評価による効率化Result:Kotlin標準の例外を値として扱う仕組みEither:Arrowなど外部ライブラリで使われる関数型データ型- Coroutine / Flow:関数型と相性が良い非同期処理モデル
まとめ
- Kotlinは純関数型言語ではない
- 関数型は「選択肢として強力に支援」されている
- 不変性・副作用分離・高階関数が設計の鍵
- OOPと対立せず、併用が前提
- inlineと非ローカルreturnは中級者の分水嶺
以上、Kotlinの関数型についてでした。
最後までお読みいただき、ありがとうございました。









