normcore.dev

関数型プログラミング

関数型プログラミング(Functional Programming)という言葉をよく耳にするようになりましたが、「難しそう」「数学の知識が必要?」といったイメージを持つ人も多いかもしれません。

この記事では、JavaScriptを使って関数型プログラミングの基本的な考え方と、実際の開発でどう役立つのかを解説します。

※2023年5月15日に内容を大幅に加筆・修正しました。

関数型プログラミングとは?

一言で言えば、**「プログラムを『計算(関数)』の組み合わせとして記述すること」**を目指すパラダイムです。

命令型プログラミングが「コンピュータへの命令の手順」を記述するのに対し、関数型プログラミングは「値がどう変換されるか」という**What(何をするか)**に焦点を当てます。

目的は、状態管理の複雑さと副作用を最小限に抑えることです。これにより、バグが少なく、予測可能で、テストしやすいコードを書くことができます。

命令型 vs 宣言型

違いを理解するために、具体的なコードを見てみましょう。「配列から偶数の数値だけを取り出し、それを2倍にする」という処理を考えます。

命令型のアプローチ

forループや一時変数を使い、「どのように処理するか(How)」を記述します。

const numbers = [1, 2, 3, 4, 5];
const doubledEvens = [];

for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    doubledEvens.push(numbers[i] * 2);
  }
}

console.log(doubledEvens); // [4, 8]

このコードは、CPUが何をするかを正確に記述していますが、「何がしたいのか」はコード全体を追わないと見えてきません。また、doubledEvens という状態を変化させています。

宣言型(関数型)のアプローチ

filtermap などを使い、「何がしたいか(What)」を記述します。

const numbers = [1, 2, 3, 4, 5];

const doubledEvens = numbers
  .filter(n => n % 2 === 0) // 偶数だけに絞り込む
  .map(n => n * 2);         // 2倍にする

console.log(doubledEvens); // [4, 8]

手続きの詳細(ループカウンタの管理や配列へのpush)が隠蔽され、処理の意図が明確です。これが関数型プログラミングの入り口です。

主要な概念

関数型プログラミングを支える重要な3つの概念を紹介します。

1. 純粋関数 (Pure Functions)

純粋関数とは、以下の2つの条件を満たす関数です。

  1. 同じ入力に対して常に同じ出力を返す
  2. 副作用がない(外部の状態を変更したり、外部の状態に依存しない)

ダメな例(不純な関数):

let count = 0;
function increment() {
  count++; // 外部変数に依存し、変更している(副作用)
  return count;
}

良い例(純粋関数):

function increment(n) {
  return n + 1; // 入力のみに依存し、常に同じ結果を返す
}

純粋関数は、実行コンテキストに依存しないため、テストが非常に簡単になります。

2. 不変性 (Immutability)

データは変更せず、新しいデータを生成します。
JavaScriptでは、const を使ってもオブジェクトのプロパティは変更できてしまいますが、関数型では意図的に変更を避けます。

/* 変更(Mutation) */
const user = { name: "Yuta", score: 10 };
user.score = 20; // 元のオブジェクトを変更

/* 不変(Immutability) */
const user = { name: "Yuta", score: 10 };
// スプレッド構文で新しいオブジェクトを作成
const updatedUser = { ...user, score: 20 };

データが不変であれば、「いつの間にかデータが変わっていた」というバグを防ぐことができます。

3. 高階関数 (Higher-Order Functions)

関数を「値」として扱います。関数を引数として受け取ったり、戻り値として関数を返したりできる関数のことです。
mapfilter も、コールバック関数を受け取る高階関数の一種です。

JavaScriptでよく使う関数型メソッド

JavaScriptには強力な配列メソッドが標準で備わっています。

map: 変換する

配列の各要素に関数を適用し、新しい配列を作ります。

const prices = [100, 200, 300];
const taxIncluded = prices.map(price => price * 1.1);
// [110, 220, 330]

filter: 選別する

条件に合う要素だけを残した、新しい配列を作ります。

const users = [
  { name: "A", active: true },
  { name: "B", active: false },
  { name: "C", active: true }
];
const activeUsers = users.filter(user => user.active);

reduce: 集約する

配列を単一の値(数値、オブジェクト、別の配列など)にまとめます。最も汎用性が高いメソッドです。

const nums = [1, 2, 3, 4];
const sum = nums.reduce((acc, current) => acc + current, 0);
// 10

実践的なメリット

なぜ関数型プログラミングを学ぶ必要があるのでしょうか?

  1. バグが減る: 副作用を抑え、データ不変にすることで、予期せぬ状態変化によるバグが激減します。
  2. テストしやすい: 純粋関数はモックやセットアップが不要で、入力と出力だけを確認すればよいため、テストが容易です。
  3. コードが読みやすい: 宣言的に書くことで、コードが「何をしているか」を語るようになります。

まとめ

関数型プログラミングは、「全てを純粋関数にしなければならない」という厳しいルールではありません。
普段のコードの中で、

  • なるべく副作用を減らす
  • データを直接書き換えない
  • 小さな純粋関数を組み合わせる

といった「関数型のエッセンス」を取り入れるだけでも、コードの品質は大きく向上します。まずは for ループを mapfilter に置き換えるところから始めてみてください。

← Back to home