関数型プログラミングの核心には「高階関数」(Higher-Order Functions)の存在があります。
高階関数は、関数を引数として受け取ったり、関数を返すことができる関数のことを指します。
JavaScriptでは、この高階関数を使用することで、コードの再利用性と柔軟性を大幅に向上させることができます。
本記事では、高階関数の基本的な概念と使用方法について詳しく説明します。
高階関数とは
高階関数とは、以下のいずれかの条件を満たす関数です。
- 関数を引数として受け取る関数
- 関数を戻り値として返す関数
高階関数を使用することで、関数をより汎用的かつ柔軟に扱うことができます。
本記事で紹介する高階関数
JavaScriptには、配列操作のための高階関数がいくつか用意されています。
その代表例がmap
, filter
, reduce
ですが、本記事ではその他の重要な高階関数(some
, every
, find
, findIndex
, sort
, forEach
)も併せて紹介します。
map
map
は、Array.prototype.map
として定義されています。
関数を引数として受け取り、配列の各要素に対して関数を適用し、新しい配列を生成して返します。
map
の使用例
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map( n => n * 2 );
console.log(doubled); // [2, 4, 6, 8, 10]
👆上記の例では、map
を使用して、配列内の各要素を2倍にしています。
map
が受け取っている引数は、関数(アロー関数)です。
この関数が配列内の要素を1つ1つ2倍にして、新しい配列として生成しています。
filter
filter
は、Array.prototype.filter
として定義されています。
配列の各要素に対して関数を適用し、指定された条件を満たす要素だけを含む新しい配列を生成します。文字通りフィルタリングしているので、覚えやすいですね。
filter
の使用例
const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = numbers.filter( n => n % 2 === 0 );
console.log(evenNumbers); // [2, 4, 6]
👆上記の例では、filter
を使用して、配列内の偶数だけを抽出しています。
filter
が受け取っている引数は、関数(アロー関数)です。
この関数が配列内の要素を1つ1つ2で割って余りが0になるもの、つまりは偶数のみを選定し、新しい配列として生成しています。
reduce
reduce
は、Array.prototype.reduce
として定義されています。
reduce
は、配列を単一の値に変換するための高階関数です。
各要素に対して累積的に関数を適用し、最終的な結果を返します。
reduce
の使用例
const numbers = [1, 2, 3, 4];
const sum = numbers.reduce( (total, n) => total + n, 0 );
console.log(sum); // 10
👆上記の例では、reduce
を使用して、配列内の数値の合計を計算しています。
受け取っているアロー関数の内容が少々難しいので、仕組みと処理の流れを説明します。
サンプルプログラムの詳細
reduceの引数と初期設定
reduce
には2つの引数が渡されます。- 第一引数はコールバック関数。今回の場合は「
(total, n) => total + n
」👈このアロー関数 - 第二引数は初期値で、今回の場合は
0
- 第一引数はコールバック関数。今回の場合は「
- 初期設定
total
は累積計算の結果を保持する変数で、初期値0
で開始します。n
は現在の配列の要素です。
各ステップの処理
- 最初のステップ
total = 0
(初期値)n = 1
(配列の最初の要素)total + n = 0 + 1 = 1
- 新しい
total
の値は1
- 次のステップ
total = 1
n = 2
(配列の次の要素)total + n = 1 + 2 = 3
- 新しい
total
の値は3
- 次のステップ
total = 3
n = 3
(配列の次の要素)total + n = 3 + 3 = 6
- 新しい
total
の値は6
- 最後のステップ
total = 6
n = 4
(配列の次の要素)total + n = 6 + 4 = 10
- 新しい
total
の値は10
some
some
は、Array.prototype.some
として定義されています。
some
は、配列の少なくとも1つの要素が指定された条件を満たす場合にtrue
を返し、すべての要素が条件を満たさない場合にはfalse
を返します。
some
の使用例
const numbers = [1, 2, 3, 4, 5];
const hasEven = numbers.some( n => n % 2 === 0 );
console.log(hasEven); // true
👆上記の例では、numbers
配列に偶数(2と4)が含まれているため、some
はtrue
を返します。
const numbers = [1, 3, 5, 7, 9];
const hasEven = numbers.some( n => n % 2 === 0 );
console.log(hasEven); // false
👆上記の例では、配列numbers
のいずれの要素も偶数(2で割り切れる)ではないため、some
はfalse
を返します。
every
every
は、Array.prototype.every
として定義されています。
every
メソッドは、配列のすべての要素が指定された関数の条件を満たすかどうかを確認します。
すべての要素が条件を満たす場合にtrue
を返し、一つでも条件を満たさない要素がある場合はfalse
を返します。
every
の使用例
const numbers = [2, 4, 6, 8];
const allEven = numbers.every( n => n % 2 === 0 );
console.log(allEven); // true
👆上記の例では、配列numbers
のすべての要素が偶数(2で割り切れる)であるため、every
はtrue
を返します。
const numbers = [2, 3, 6, 8];
const allEven = numbers.every( n => n % 2 === 0 );
console.log(allEven); // false
👆上記の例では、配列mixedNumbers
に条件を満たさない要素(3)が含まれているため、every
はfalse
を返します。
find
find
は、Array.prototype.find
として定義されています。
find
は、配列の中から指定された関数の条件を満たす最初の要素を返します。条件を満たす要素が見つからない場合はundefined
を返します。
条件を満たす要素が複数ヒットしても、返されるのは最初の要素だけであることに注意してください。
find
の使用例
const numbers = [1, 2, 3, 4, 5];
const firstEven = numbers.find( n => n % 2 === 0 );
console.log(firstEven); // 2
👆上記の例では、配列numbers
の要素中2と4が条件に合致しますが、返されるのは条件を満たす最初の要素だけなので2を返します。
findIndex
findIndex
は、Array.prototype.findIndex
として定義されています。
findIndex
は、配列の中から指定された関数の条件を満たす最初の要素のインデックスを返します。条件を満たす要素が見つからない場合は-1
を返します。
findIndex
の使用例
const numbers = [1, 2, 3, 4, 5];
const firstEvenIndex = numbers.findIndex( n => n % 2 === 0 );
console.log(firstEvenIndex); // 1
👆上記の例では、配列numbers
の要素中2と4が条件に合致しますが、返されるのは条件を満たす最初の要素のインデックスだけなので要素「2」のインデックス(添え字)である1を返します。
sort
sort
は、Array.prototype.sort
として定義されています。
sortは、配列の要素を指定された関数に基づいてソートします。デフォルトでは文字列としてソートされますが、カスタムのソート関数を指定することも可能です。
sort
の使用例(昇順ソート)
const numbers = [4, 2, 5, 1, 3];
numbers.sort( (a, b) => a - b );
console.log(numbers); // [1, 2, 3, 4, 5]
sort
の使用例(降順ソート)
数値を降順にソートするためには、ソート関数として (a, b) => b - a
を使用します。
const numbers = [4, 2, 5, 1, 3];
numbers.sort( (a, b) => b - a );
console.log(numbers); // [5, 4, 3, 2, 1]
カスタムソートのサンプル①
文字列をアルファベット順(昇順)にソートする場合は、デフォルトのsort
関数をそのまま使用できますが、大文字小文字を区別しないカスタムソート関数を指定するサンプルを示します。
const fruits = ['Banana', 'apple', 'Cherry', 'date'];
fruits.sort( (a, b) => a.toLowerCase().localeCompare(b.toLowerCase()) );
console.log(fruits); // ['apple', 'Banana', 'Cherry', 'date']
カスタムソートのサンプル②
オブジェクトのプロパティを基にソートする場合も、カスタムソート関数を使用します。
例えば、オブジェクト配列を特定のプロパティ値でソートする場合です。
const items = [
{ name: 'apple', quantity: 2 },
{ name: 'banana', quantity: 4 },
{ name: 'cherry', quantity: 1 }
];
// quantity(数量)に基づいて昇順にソート
items.sort( (a, b) => a.quantity - b.quantity );
console.log(items);
▼ (3) [{…}, {…}, {…}]
▶ 0: {name: 'cherry', quantity: 1}
▶ 1: {name: 'apple', quantity: 2}
▶ 2: {name: 'banana', quantity: 4}
forEach
forEach
は、以下の3種類として定義されています。
Array.prototype.forEach
Map.prototype.forEach
Set.prototype.forEach
forEach
は、コレクション(Array、Map、Set)の各要素に対して指定された関数を一度ずつ実行します。
戻り値はなく、副作用を発生させるために使用されることが多いです。
forEach
の使用例
const numbers = [1, 2, 3, 4, 5];
numbers.forEach( n => console.log(n) );
> 1
> 2
> 3
> 4
> 5
forEach
を繰り返し処理(ループ)で使用する際の注意点
forEach
の特性
- 破壊的ではない
forEach
は元の配列を変更しません。配列の各要素に対して指定された関数を実行するだけです。
- 返り値がない
forEach
メソッド自体は何も返しません。副作用を発生させるために使用されることが多いです。
- 非同期操作に適さない
forEach
は同期的に動作するため、非同期操作(例えば、async/await
)との相性が良くありません。非同期処理を行う場合は、for...of
ループやPromise.all
などを検討する方が良いです。
forEach
の副作用
forEach
は配列の要素自体を変更しないため、配列そのものに対する破壊的な変更は行いません。
しかし、forEach
内で実行される関数が外部の状態を変更する場合、それが副作用となります。
例えば、関数内で他の変数を変更したり、ログを出力したり、データベースにアクセスしたりすることなどが該当します。
注意点
- breakやcontinueが使えない
forEach
ループ内で、break
やcontinue
を使用してループを中断することはできません。ループを中断したい場合は、for
ループやfor...of
ループを使用しましょう。
- 非同期処理に注意
forEach
内で非同期関数を使用する場合、期待通りに動作しないことがあります。非同期処理を行う場合は、for...of
ループやPromise.all
を使用することが推奨されます。
- パフォーマンスの考慮
forEach
は大きなデータセットに対して使用するとパフォーマンスに影響を与えることがあります。他のループ構造やメソッド(map
,reduce
など)を検討することが重要です。
まとめ
高階関数は、JavaScriptの関数型プログラミングにおいて重要な役割を果たします。
関数を引数として受け取るmap
, filter
, reduce
を始めとして、forEach
, some
, every
, find
, findIndex
, sort
など、さまざまな高階関数を利用することで、より柔軟で再利用性の高いコードを書くことができます。
それぞれの使いどころ
map
、filter
は、新しい配列を生成する際に使います。reduce
は値をまとめたい場合に適します。- 「Promise + 順次処理」で使用される場合もあります。
some
、every
は条件判定に便利です。find
、findIndex
は特定の要素を見つけたい場合に利用します。sort
は並び替えが必要な時に使えます。forEach
は単純な反復処理が必要な場合に便利です。
次回の記事では、関数の合成やカリー化など、さらに高度な関数型プログラミングのパターンについて探求していきます。
本記事についての質問、誤りの指摘、ご意見ご感想などありましたら、ぜひコメント頂ければ幸いです。
最後までお読みいただき、ありがとうございました。
コメント