関数型プログラミングを学ぶ上で「純粋関数」という概念は最も重要な基礎知識の一つです。
純粋関数を理解することで、テストしやすく予測可能なコードを書く力が大きく向上します。
本記事では、純粋関数の意味・特徴・副作用なしという性質・関数型プログラミングとの関係・不純な関数との違い・メリットをわかりやすく解説していきます。
関数型プログラミングに興味のある方・コードの品質を高めたいエンジニアの方にぜひ参考にしていただける内容です。
純粋関数の考え方は、関数型言語(Haskell・Elm)だけでなく、JavaScript・Python・Java・Kotlinなどのマルチパラダイム言語でも積極的に活用されています。
シンプルな概念ですが、コードの品質・テスタビリティ・保守性に対して非常に大きな影響を持つ重要な考え方です。
純粋関数とは何か?2つの重要な条件と基本的な意味
それではまず、純粋関数の基本的な意味と2つの重要な条件について解説していきます。
純粋関数(Pure Function)とは、以下の2つの条件を満たす関数です。
純粋関数の2つの条件
条件①:同じ入力に対して常に同じ出力を返す(参照透明性)
条件②:副作用(Side Effect)を持たない
この2つの条件を両方満たしている関数が「純粋関数」であり、どちらか一方でも違反すると「不純な関数」になります。
「同じ入力に対して常に同じ出力」とは、関数の結果が入力だけで決まり、外部の状態に依存しないことを意味します。
「副作用がない」とは、関数の実行が外部の状態を変更しないことを意味します。
【純粋関数と不純な関数の比較(JavaScript例)】
// 純粋関数の例
function add(a, b) {
return a + b; // 同じ入力→常に同じ出力。外部に影響なし
}
add(2, 3); // 常に5を返す
// 不純な関数の例①(外部状態に依存する)
let tax = 0.1;
function calculatePrice(price) {
return price * (1 + tax); // taxという外部変数に依存→不純
}
// 不純な関数の例②(副作用がある)
let count = 0;
function increment() {
count++; // 外部変数を変更→副作用あり→不純
return count;
}
純粋関数はどれだけ呼び出しても外部に影響を与えず、入力さえわかれば結果が完全に予測できます。
この性質が、テストのしやすさ・デバッグのしやすさ・コードの理解しやすさに直結します。
副作用とは何か?不純な関数が引き起こす問題を理解する
続いては、副作用の意味と不純な関数が引き起こす問題について確認していきます。
副作用(Side Effect)とは、関数が実行されることによって、関数の外部の状態が変化することです。
副作用の具体的な例としては以下のようなものがあります。
| 副作用の種類 | 具体例 |
|---|---|
| グローバル変数の変更 | 関数内でグローバル変数を書き換える |
| データベースの操作 | DB へのINSERT・UPDATE・DELETE |
| ファイルの読み書き | ファイルシステムへの書き込み |
| コンソール出力 | console.log・print等の出力 |
| ネットワーク通信 | APIへのHTTPリクエスト |
| 引数として受け取ったオブジェクトの変更 | 配列・オブジェクトをミューテーション |
副作用があると「この関数を呼んだら何が起きるか」を関数の定義だけから予測できなくなります。
テストを書く際も外部状態のセットアップ・クリーンアップが必要になり、テストが複雑になります。
副作用を排除した純粋関数は、モック・スタブなしで単体テストが書けるため、テストコストが大幅に削減されます。
もちろん、データベースへの保存・API通信・ファイル操作など副作用が必要な処理も現実のアプリケーションには欠かせません。
関数型プログラミングでは「副作用を完全になくす」のではなく「副作用を関数の境界に集め、ビジネスロジックを純粋関数で表現する」という設計が推奨されます。
純粋関数のメリット:テスタビリティ・可読性・並列処理への対応
続いては、純粋関数のメリットについて確認していきます。
純粋関数を積極的に使うことで、コードの品質が複数の観点から向上します。
【純粋関数の主要なメリット】
メリット① テストが簡単
入力と期待される出力を指定するだけでテストが書ける。外部環境のセットアップが不要。
メリット② デバッグが容易
バグの原因を特定しやすい。同じ入力で常に同じ結果が返るため再現が確実。
メリット③ コードの可読性向上
関数が入力と出力の関係だけを表現するため、コードの意図が明確になる。
メリット④ 参照透明性による最適化
同じ入力への呼び出し結果をキャッシュ(メモ化)することで計算を効率化できる。
メリット⑤ 並列処理の安全性
外部状態を変更しないため、複数スレッドから安全に呼び出せる。競合状態が発生しない。
特にメモ化(Memoization)の恩恵は大きく、同じ引数で繰り返し呼ばれる計算コストの高い純粋関数の結果をキャッシュすることで、パフォーマンスを大幅に向上できます。
Reactの「useMemo」フックもこの原理を活用したものです。
並列処理においては、純粋関数が共有状態を変更しないためデータ競合(Race Condition)が起きません。
マルチスレッド・マルチコアの活用が重要な現代のアプリケーションで、純粋関数は安全な並列処理の基盤となります。
関数型プログラミングにおける純粋関数の位置づけ
続いては、関数型プログラミングにおける純粋関数の位置づけについて確認していきます。
関数型プログラミング(Functional Programming)において、純粋関数は最も根本的な構成要素です。
Haskell・Elmのような純粋関数型言語では、副作用のある処理はモナド(IO Monad)という仕組みで明示的に分離されます。
JavaScriptでは純粋関数とイミュータブル(不変)なデータ構造を使う関数型スタイルが、RamdaやFP-TSなどのライブラリで実践されています。
「ビジネスロジックを純粋関数で表現し、副作用(DB・API・UI)をエッジに追い出す」というアーキテクチャ設計の考え方は、現代のソフトウェア開発で広く採用されています。
純粋関数の考え方はReact・Redux・Elmなどのフロントエンドフレームワーク設計にも大きな影響を与えており、UIの状態管理を予測可能にするための基盤となっています。
まとめ
純粋関数は「同じ入力に対して常に同じ出力を返す」「副作用を持たない」という2つの条件を満たす関数です。
副作用を持つ不純な関数と比較して、テストのしやすさ・デバッグの容易さ・可読性・並列処理の安全性・メモ化によるパフォーマンス最適化など多くのメリットがあります。
関数型プログラミングでは純粋関数を中心にビジネスロジックを表現し、副作用を関数の境界に集める設計が推奨されます。
言語を問わず活用できる考え方であり、コードの品質・保守性を高めるための重要な設計原則です。
純粋関数の考え方を日々のコーディングに取り入れることで、より予測可能で保守しやすいコードが書けるようになるでしょう。