こんにちは、私は東京からのフルスタックWebデベロッパーであるTUANです。
今後の便利で面白い記事を見逃さないように、私のブログをフォローしてください。
JavaScriptでFirst-Class
関数を聞いたことがありますか?いいえ、それらはエリート関数だけのクラブではありません。それらは単にあなたのコードで他のデータタイプと同じように扱われる関数です。これは大きな問題には見えませんが、信じてください。それはきれいで効率的で再利用可能なコードを書くためのすべての可能性を開くものです。
この記事では、JavaScript
で関数をfirst-class citizen
にするものについて掘り下げ、この強力な機能を使用してコーディングスキルをレベルアップする5つのリアルワールドの例を紹介します。
「First-Class」関数とは何ですか?
JavaScript
(およびその他の多くのプログラミング言語)では、関数が他の関数に渡されたり、関数から値として返されたり、変数に割り当てられたりすると、それらはfirst-class citizen
と見なされます。
これは最初は少し混乱するかもしれませんので、いくつかの例を使って解説します。
関数を変数に割り当てる
JavaScript
では、他の値と同じように関数を変数に割り当てることができます。例えば:
const addTwoNumbers = function(x, y) {
return x + y;
};
ここでは、x
とy
を引数として取るaddTwoNumbers
という関数を作成しました。そしてこの関数をaddTwoNumbers
という変数に割り当てています。
関数を引数として渡す
First-Class
関数を使用する最も強力な方法は、それらを他の関数の引数として渡すことです。これを「高階関数・Higher-order function
」と呼びます。
例えば、数値の配列があって、すべての偶数の合計を求めたいとします。これを高階関数とヘルパー関数を使って次のように行うことができます:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const isEven = function(num) {
return num % 2 === 0;
};
const sum = function(accumulator, currentValue) {
return accumulator + currentValue;
};
const evenSum = numbers.filter(isEven).reduce(sum);
console.log(evenSum); // 30
ここで、配列と関数を引数とする高階関数filterを作成しました。filterに渡される関数(この場合はisEven
)は、配列内の要素をテストするために使用され、trueを返す要素のみが新しい配列に保持されます。
また、accumulator
とcurrent value
を引数とするsum
という関数を作成しました。この関数は、各要素に対して和を返す関数です。この関数は、初期値を0とするアキュムレーター値を使用して配列を反復処理して、sum
関数を適用する別の高階関数reduceに渡されます。
関数を値として返す
JavaScript
では、関数を他の関数から値として返すこともできます。これは、他の関数を生成または修正する関数を作成するのに便利です。
例えば、引数を特定の数で乗算する新しい関数を返す関数を作成したいとします。これは、次のように行うことができます。
const multiplier = function(x) {
return function(y) {
return x * y;
};
};
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(4)); // 8
console.log(triple(4)); // 12
ここで、1つの引数xを取るmultiplier
という関数を作成しました。2番目の引数yを取る新しい関数を返し、x
とyの積を返します。それから、multiplier
を異なる引数で呼び出すことで、2つの新しい関数double
とtriple
を作成しました。
例
第一級の関数の基本を学んだので、これらを使用して、コードをより強力で表現力豊かにする方法を5つの簡単な例で見てみましょう。
部分関数適用
部分関数適用は、既存の関数のいくつかの引数を部分的に適用(または「事前に埋める」)することで、新しい関数を作成するテクニックです。これは、関数の特化版を作成するのに便利であるか、他のライブラリやフレームワークで動作するように関数を適用するのに役立ちます。
例えば、2つの引数を取って和を返すadd
という関数があるとします。この関数を使用して、常に引数に10を加える部分的に適用されたバージョンを作成することができます:
const add = function(x, y) {
return x + y;
};
const add10 = add.bind(null, 10);
console.log(add10(5)); // 15
ここで、bind
メソッドを使用して、初期のadd
関数と同じように動作するadd10
という新しい関数を作成しました。ただし、最初の引数は常に10に設定されています。これにより、特定の使用ケースに合わせたadd
関数の特化版を作成することができます。
関数の組合せ
関数の組合せとは、複数の関数を組み合わせて、1つの関数呼び出しですべてのステップを実行する新しい関数を作成するプロセスです。これは、よりモジュール化された再利用可能なコードを作成するのに役立ち、複雑なアルゴリズムを単純化するのにも役立ちます。
例えば、単純な数学演算を実行する2つの関数multiply
とaddがあるとします。これらを組み合わせて、両方の演算を1つの呼び出しで実行する新しい関数calculate
を作成することができます:
const multiply = function(x, y) {
return x * y;
};
const add = function(x, y) {
return x + y;
};
const calculate = function(x, y) {
return add(multiply(x, y), x);
};
console.log(calculate(2, 3)); // 7
ここで、x
およびy
を引数とするcalculate
という関数を作成しました。この関数は、multiply
にx
およびy
を引数として呼び出した結果を返し、その結果を引数としてadd
を呼び出すことで、両方の関数の機能を組み合わせた1つの再利用可能な関数を作成します。
メモ化
メモ化とは、関数呼び出しの結果をキャッシュに格納し、可能な場合に再利用することで、再帰的または高価な関数のパフォーマンスを向上させるテクニックです。これは、同じ計算を複数回実行するコードを最適化するのに役立ち、繰り返し計算を実行するアルゴリズムの効率を改善するのにも役立ちます。
例えば、フィボナッチ数列の第n番目の数を計算するfibonacci
という関数があるとします。この関数のパフォーマンスを向上させるために、次のようにメモ化を使用することができます。
const fibonacci = function(n) {
if (n <= 1) {
return n;
}
if (!fibonacci.cache) {
fibonacci.cache = {};
}
if (fibonacci.cache[n]) {
return fibonacci.cache[n];
}
const result = fibonacci(n - 1) + fibonacci(n - 2);
fibonacci.cache[n] = result;
return result;
};
console.log(fibonacci(10)); // 55
ここで、fibonacci
関数に以前の関数呼び出しの結果を格納するキャッシュオブジェクトを追加しました。関数が呼び出されるたびに、最初にキャッシュを確認して、結果がすでに計算されているかどうかを確認します。既に計算されている場合は、キャッシュされた結果を返します。されていない場合は、結果を計算して、将来の利用のためにキャッシュに保存します。これにより、不要な計算を回避することで、fibonacci
関数のパフォーマンスを大幅に向上させることができます。
カリー化
カリー化は、既存の関数のいくつかの引数を「事前に埋める」ことで新しい関数を作成するテクニックです。これは、関数の特化版を作成するのに便利であるか、他のライブラリやフレームワークで動作するように関数を適用するのに役立ちます。
例えば、2つの引数を取って和を返すadd
という関数があるとします。この関数を1回に1つの引数を取るようにカリー化されたバージョンを作成することができます:
const add = function(x) {
return function(y) {
return x + y;
};
};
const add10 = add(10);
console.log(add10(5)); // 15
ここで、add10
というyと10
の和を返す、add
関数のカリー化されたバージョンを作成しました。これにより、特定の使用ケースに合わせたadd
関数の特化版を作成することができます。
カスタムイテレータ
カスタムイテレータは、カスタム方法で特定のデータ構造やデータコレクションを反復処理するために使用できる関数です。これは、map
やreduce
のような組み込み関数の特化版を作成するのに役立ち、また、配列やオブジェクトのように振る舞うカスタムデータ構造を実装するのにも役立ちます。
例えば、リンクリストデータ構造があり、リストを先頭から末尾に向けてトラバースするカスタムイテレータを作成したいとします。次のようにすることができます。
class LinkedList {
constructor(head, tail) {
this.head = head;
this.tail = tail;
}
}
LinkedList.prototype[Symbol.iterator] = function*() {
let current = this.head;
while (current) {
yield current.value;
current = current.next;
}
};
const list = new LinkedList(
{ value: 1, next: { value: 2, next: { value: 3, next: null } } },
{ value: 3, next: null }
);
for (const value of list) {
console.log(value);
}
// Output: 1, 2, 3
ここで、リンクリストデータ構造を表すLinkedList
というクラスを作成しました。その後、Symbol.iterator
シンボルを使用して、LinkedList.prototype
にカスタムイテレータ関数を追加しました。この関数は、ジェネレータ関数を使用して、先頭ノードから末尾ノードまで、1つずつリンクリストノードの値を返すようにします。
そして、list
オブジェクトを反復処理するために、for...of
ループを使用して、各ノードの値をコンソールにログすることができます。これにより、組み込みのfor...of
ループ構文を使用して、簡単かつ効率的にリンクリストをトラバースすることができます。
結論
「First-Class」関数は、JavaScript
の強力な機能であり、関数をコード内の他のデータ型のように扱うことができます。高階関数を作成するために使用するか、パフォーマンスを最適化するために使用するか、カスタムデータ構造を実装するために使用するかにかかわません、第一級関数は、クリーンで効率的で再利用可能なコードを書くための豊富な可能性を提供します。
いつものように、この記事を楽しんで新しいことを学んでいただけたと思います。
ありがとうございました。次の記事でお会いしましょう!
この記事が気に入ったら、いいねをして購読してサポートしてください。ありがとうございます。