基礎●●●○○

関数型プログラミング

Functional Programmingかんすうがたプログラミング

副作用を抑え、純粋関数の合成としてプログラムを組み立てる様式。宣言的で予測しやすいコードを目指す。

概要

関数型プログラミング(FP: Functional Programming)は、プログラムを「状態を書き換える命令の列」ではなく「値を受け取って値を返す関数の合成」として組み立てる様式です。中心にあるのは、同じ入力には必ず同じ出力を返し、外部の状態を一切変更しない「純粋関数」という考え方で、データは書き換えずに新しい値を作るイミュータビリティ(不変性)とセットで使われます。

Haskell や Elm のような専用言語だけの話ではありません。JavaScript の map / filter、React の「UI は状態の関数」という設計、Rust のイテレータチェーン、Java の Stream API — 現代の主流言語・フレームワークには関数型の考え方が深く浸透しており、意識せずに毎日使っている人がほとんどです。純粋な関数型言語を書かない人にとっても、「どこまでを純粋に保ち、どこで副作用を起こすか」という設計感覚として効いてくる語彙です。

「副作用」とは、戻り値を返す以外に外の世界へ影響を与えること — 変数の書き換え、ファイルやデータベースへの書き込み、画面出力、現在時刻の取得などを指します。関数型プログラミングは副作用を「禁止」するのではなく、プログラムの端に押し出して、中心部を予測可能な純粋関数だけで組み立てることを目指します。

なぜ生まれたか

理論的なルーツは1930年代のラムダ計算(計算を関数の適用だけで表す数学体系)まで遡りますが、実務の世界で関数型が再評価されたのは、共有される可変状態こそがバグの温床だと痛感されたからです。複数の関数やモジュールが同じ変数を書き換えるプログラムでは、ある関数の結果が「いつ・誰が・何を書き換えたか」という履歴に依存します。再現しないバグ、順番を入れ替えると壊れる処理、モックだらけになるテスト — その多くは、状態の書き換えがプログラム全体に散らばっていることが原因です。マルチコア時代に入り、並行処理で複数スレッドが同じ状態を触ると競合が起きることが決定打になりました。

関数型プログラミングの答えは、原因を消してしまうことです。データを書き換えず、関数は入力だけから出力を計算する。そうすればどの関数も単独で理解・テストでき、実行順序や並列化に怯える必要がなくなります。副作用が必要な処理は境界に集め、「純粋な中心部+薄い副作用の殻」という構造にする — この規律が、規模と並行性が増した現代のソフトウェアで実用的な価値を持つようになりました。

詳細

純粋関数と参照透過性

純粋関数は「同じ引数なら必ず同じ戻り値」「外部への影響ゼロ」の2条件を満たす関数です。この性質は参照透過性とも呼ばれ、「関数呼び出しをその結果の値に置き換えてもプログラムの意味が変わらない」ことを意味します。消費税込み価格(1000) はいつどこで呼んでも同じ値ですが、現在の残高を取得() は呼ぶタイミングで結果が変わる不純な関数です。参照透過であれば、結果をキャッシュ(メモ化)しても、遅延評価しても、並列に計算しても安全です。純粋関数のテストは「入力を渡して戻り値を確かめる」だけで済み、モックも準備も要りません。この書き換えない規律を支えるのがイミュータビリティで、配列に要素を足すときも元の配列は触らず、新しい配列を作って返します。

共有可変状態純粋関数のパイプライン共有状態みんなが書き換える処理A処理B処理C処理D結果が実行順と履歴に依存する入力の取得副作用変換 f純粋絞り込み g純粋集計 h純粋保存・表示副作用データは一方向に流れ、副作用は境界だけに置く
共有状態を書き換える構造(左)と、純粋関数のパイプライン+境界の副作用(右)の比較

高階関数 — map・filter・reduce

関数型のもう1つの柱が、関数を値として扱えることです。関数を引数に取ったり戻り値として返したりする関数を高階関数と呼びます。その代表が、コレクション処理の三種の神器 map(全要素を変換する)・filter(条件に合う要素を残す)・reduce(畳み込んで1つの値にする)です。

const total = orders
  .filter(o => o.paid)          // 支払い済みだけ残す
  .map(o => o.price * 1.1)      // 税込みに変換
  .reduce((sum, p) => sum + p, 0); // 合計へ畳み込む

for ループでカウンタと途中結果の変数を書き換えていく書き方と比べると、「何をするか」だけが宣言的に並び、「どう繰り返すか」が消えています。ここで渡している小さな関数が外の変数を捕まえられるのはクロージャの働きで、ループの代わりに自分自身を呼ぶ再帰とともに、関数型スタイルを支える基礎部品です。

副作用の分離 — 純粋な中心と不純な殻

現実のプログラムは副作用なしには成立しません。だからこそ設計の焦点は「副作用をどこに置くか」になります。よく知られる指針が「Functional Core, Imperative Shell」— ビジネスロジックの中心部(何をいくらにするか、どう並べ替えるかといった判断)は純粋関数だけで書き、データベース読み書きや API 呼び出しといった副作用は外側の薄い殻に集める、という構造です。中心部は入出力の値だけでテストでき、殻は「純粋な中心の結果を外界へ運ぶだけ」の単純な役割に痩せていきます。テストが書きにくい・再現しないバグが多いと感じたら、副作用が中心部に染み込んでいるサインです。

主流言語への浸透

この10年で関数型の道具立ては主流言語の標準装備になりました。React は「UI は状態を受け取って画面を返す関数」というモデルを採り、状態管理ライブラリの Redux は「新しい状態 = reducer(現在の状態, アクション)」という純粋関数そのものです。画面の差分更新を安全に自動化できる仮想DOMも、描画が状態の純粋な関数であることが前提です。Rust のイテレータは map / filter のチェーンをゼロコストで最適化し、所有権システムは可変な共有を型で禁じます。Java の Stream API、C# の LINQ、Python の内包表記も同じ系譜です。データが変わったら計算し直すというリアクティビティの考え方も、値の流れを関数の合成で記述する関数型の発想と地続きです。

注意したいのは、関数型とオブジェクト指向は二者択一ではないことです。実務では、ドメインの境界と不変条件はオブジェクトで守り、その内側のデータ変換は純粋関数で書く、という併用が自然な着地点になります。まずは「この関数、純粋にできないか?」と自問する習慣から始めるのが、いちばん実りのある入り口です。