Blog#91: JavaScriptコードを革新する「First-Class」関数のサプライズする方法

image.png

こんにちは、私は東京からのフルスタック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;
};

ここでは、xyを引数として取る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を返す要素のみが新しい配列に保持されます。

また、accumulatorcurrent 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つの新しい関数doubletripleを作成しました。

第一級の関数の基本を学んだので、これらを使用して、コードをより強力で表現力豊かにする方法を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という関数を作成しました。この関数は、multiplyxおよび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の強力な機能であり、関数をコード内の他のデータ型のように扱うことができます。高階関数を作成するために使用するか、パフォーマンスを最適化するために使用するか、カスタムデータ構造を実装するために使用するかにかかわません、第一級関数は、クリーンで効率的で再利用可能なコードを書くための豊富な可能性を提供します。

いつものように、この記事を楽しんで新しいことを学んでいただけたと思います。

ありがとうございました。次の記事でお会いしましょう!

この記事が気に入ったら、いいねをして購読してサポートしてください。ありがとうございます。

NGUYỄN ANH TUẤN

Xin chào, mình là Tuấn, một kỹ sư phần mềm đang làm việc tại Tokyo. Đây là blog cá nhân nơi mình chia sẻ kiến thức và kinh nghiệm trong quá trình phát triển bản thân. Hy vọng blog sẽ là nguồn cảm hứng và động lực cho các bạn. Hãy cùng mình học hỏi và trưởng thành mỗi ngày nhé!

Đăng nhận xét

Mới hơn Cũ hơn