関数合成 かんすうごうせい
簡単に言うとこんな感じ!
「洗う→すすぐ→乾かす」みたいに、小さな処理をベルトコンベアのようにつなげて、大きな処理を作る仕組みだよ!それぞれの工程がシンプルだから、組み合わせ次第でどんな複雑な処理でも作れるんだ。
関数合成とは
関数合成(Function Composition)とは、「ある関数の出力を別の関数の入力として渡す」ことを繰り返し、複数の小さな関数を1本のパイプラインとしてつなげるプログラミングの技法です。数学の合成関数 f(g(x)) をそのままコードで表現したものと言えます。
たとえば「文字列を小文字にする」関数と「空白を取り除く」関数を合成すると、「小文字にして空白も除く」という新しい関数が生まれます。処理を細かく分割して部品化し、それを組み合わせることで複雑な機能を実現するのが関数合成の本質です。
関数合成は関数型プログラミング(Functional Programming)の中核的な考え方のひとつで、コードの再利用性・テストのしやすさ・バグの追いにくさを大幅に改善します。「1つの関数が1つのことだけをする」というシンプルな設計原則と非常に相性が良い手法です。
関数合成の仕組み
関数合成のイメージは「処理のベルトコンベア」です。データが左から右へ流れ、各ステーションで変換が施されます。
入力データ
│
▼
関数 A(小文字化)
│
▼
関数 B(空白除去)
│
▼
関数 C(先頭大文字化)
│
▼
出力データ
数学的には h(x) = f(g(x)) と書きます。コードでは次のように表現されます。
| 言語 | 合成の書き方 |
|---|---|
| JavaScript | const h = x => f(g(x)) / ライブラリで compose(f, g) |
| Python | def compose(f, g): return lambda x: f(g(x)) |
| Haskell | h = f . g(. 演算子が合成そのもの) |
| Scala | val h = f compose g |
覚え方:「右から左」に読む
合成した関数 compose(f, g) は、実行順序は g → f(右から左)です。数学の記法 f∘g と同じで「g を先にやって、その結果を f に渡す」順番になります。混乱しやすいポイントなので「compose は右が先!」と覚えておきましょう。
パイプライン演算子(|>)を使う言語やライブラリでは左から右に読めるので、直感的に理解しやすくなります。
合成できる関数の条件
| 条件 | 説明 |
|---|---|
| 出力と入力の型が合う | 前の関数の出力型 = 次の関数の入力型でないとつなげられない |
| 純粋関数であること | 副作用(ファイル書き込みなど)がないと予測可能な合成になる |
| 1つの引数を取る | 複数引数の関数はカリー化して1引数に変換すると合成しやすい |
歴史と背景
- 1930年代 — 数学者アロンゾ・チャーチがラムダ計算(λ計算)を提唱。関数の合成という概念の数学的基盤が生まれる
- 1958年 — LISPが誕生。関数を値として扱える最初期のプログラミング言語として、合成の概念を実装に持ち込む
- 1970年代 — MLやHaskellの前身となる研究が進み、型システムと関数合成の相性の良さが証明される
- 1990年 — Haskellが公開。
.(ドット演算子)による簡潔な合成記法が普及し、関数合成がプログラミング言語機能として一般化 - 2010年代 — JavaScript・Python・Scalaなど主流言語でも関数型スタイルが取り入れられ、
compose/pipe系のユーティリティが広く使われるようになる - 現在 — フロントエンド(React / Redux)・バックエンド・データ処理など、あらゆる領域で関数合成の考え方が活用されている
関連する技術・概念との比較
関数合成は単独で使う技法ではなく、関連する概念とセットで理解するとより深く使いこなせます。
| 概念 | 関係 | 一言説明 |
|---|---|---|
| 純粋関数 | 合成の前提条件 | 副作用がないから安全につなげられる |
| カリー化 | 合成を助ける変換 | 多引数関数を1引数ずつに分解し合成可能にする |
| 高階関数 | 合成を実現する仕組み | 関数を引数や戻り値として扱えるから合成ができる |
| パイプライン | 合成の書き方の一種 | 左→右の順に読める合成スタイル(` |
| モナド | 合成の発展形 | 非同期・エラー・副作用などを含む文脈付きの合成 |
パイプラインとの違い
「合成」と「パイプライン」はほぼ同じことを指しますが、書き方と読み方向が逆です。
// 合成(compose): 右から左に実行
const result = compose(capitalize, trim, toLowerCase)(input);
// ↑ 実行順: toLowerCase → trim → capitalize
// パイプライン(pipe): 左から右に実行(直感的)
const result = pipe(toLowerCase, trim, capitalize)(input);
// ↑ 実行順: toLowerCase → trim → capitalize
ビジネス的な処理フローを説明するときはパイプラインの書き方のほうが読みやすいため、現代の多くのライブラリ(Ramda, fp-ts, RxJSなど)ではパイプラインスタイルが好まれます。
実務での活用シーン
関数合成は理論だけでなく、実際の開発現場でも広く使われています。
| シーン | 具体例 |
|---|---|
| データ変換 | APIレスポンスの正規化・フィルタ・整形をパイプラインで処理 |
| フォームバリデーション | 「必須チェック→文字数チェック→形式チェック」を合成 |
| ミドルウェア | Express.jsのミドルウェア、Reduxのenhancerはまさに関数合成 |
| ログ・認証の横断処理 | ロギングや認証処理を合成して本処理に被せる |
| データパイプライン | ETL処理(Extract→Transform→Load)を関数合成で表現 |