Kotlinの関数型について

採用はこちら

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:遅延評価による効率化
  • ResultKotlin標準の例外を値として扱う仕組み
  • EitherArrowなど外部ライブラリで使われる関数型データ型
  • Coroutine / Flow:関数型と相性が良い非同期処理モデル

まとめ

  • Kotlinは純関数型言語ではない
  • 関数型は「選択肢として強力に支援」されている
  • 不変性・副作用分離・高階関数が設計の鍵
  • OOPと対立せず、併用が前提
  • inlineと非ローカルreturnは中級者の分水嶺

以上、Kotlinの関数型についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次