土曜日, 6月 7, 2025
- Advertisment -
ホームニューステックニュースJavaScriptジェネレータ関数とユーティリティで楽に配列を生成する #TypeScript - Qiita

JavaScriptジェネレータ関数とユーティリティで楽に配列を生成する #TypeScript – Qiita



JavaScriptジェネレータ関数とユーティリティで楽に配列を生成する #TypeScript - Qiita

uhyo さんのジェネレータ関数についての記事に触発されました。

僕もたびたび実務でジェネレータ関数を活用しているので、僕からも軽い TIPS を共有しておきます。

結論

このような generateArray 関数を作っておくと、「ジェネレータ関数を使って配列を作る」処理が楽に書けます。

プロジェクト全体で利用可能な汎用ユーティリティとして用意しましょう。

src/utils/generateArray.ts

/**
 * 渡されたジェネレータ関数を実行して、配列にする。
 */
export const generateArray = T>(
  generatorFn: () => GeneratorT, void>,
): T[] => {
  return Array.from(generatorFn());
};

ネストの深さが減る

標準の Array.from だけで書く場合(Before)と、 generateArray (After)を使う場合を見比べてみましょう。

Prettier を使ってフォーマットすると、以下のようになります。

// Before
const nums1 = Array.fromnumber>(
  (function* () {
    yield 1;
    yield* [2, 3];
  })(),
);
// After
const nums2 = generateArraynumber>(function* () {
  yield 1;
  yield* [2, 3];
});

Before だと yield の場所が3段階目になっていますが、After では 2段階目になっています。

ネストの深さが減るだけでも、全体の構造をつかむのが少しラクになると思います。

脳内スタックの深さも減る

しかし、それだけではありません。読む人の脳内の「スタック」 の深さも減ります。

Before では IIFE (即時実行関数式, (function *() { /* ... */ })())を使っていますが、 After では不要になっています(generateArray がジェネレータ関数を実行してくれるからです。)

即時実行関数式は、コードを読む人にとっては負担がそこそこ大きいイディオムです。

コードを上から順に読み進めるときに、

// Before
const nums1 = Array.fromnumber>(
  (function* () {
    // ^ お、ここから関数やな
    yield 1;
    yield* [2, 3];
  })(),
  // ^ お、関数の後ろに `()` がついてるから、
  //   この式全体の結果は「関数型そのもの」じゃなくて、
  //   「関数の返り値」になるんやな
);

という順をたどります。

つまり、「これは結局『関数そのもの』なのか?それとも『関数の実行結果の値』なのか?」という Ambiguity(両義性, いわゆる「あいまいさ」)を抱えたまま読み進めて、最後の () に到達してやっと カタルシスを得る Ambiguity が解消されることになります。

コード例はジェネレータ関数の中身が2行だけなのでカワイイものですが、このジェネレータ関数が長大になるにつれて負荷が大きくなるのは、容易に想像していただけると思います。

generateArray のような形のユーティリティ関数には、この IIFE による「読むときの負担」を軽減する効果もあるのです。

同様の効果が得られる、既存の有名なユーティリティ等の例:

誤returnも防止できる

オマケとして、generateArray は、型による「良い感じのガードレール」として機能してくれます。

一度 generateArray ユーティリティをつくてしまえば、その使用者は Generator 型の詳しい知識不要でこれを使えるのでお得です。

Before だと「number の配列を作ろうとしてたのに、違う型の値を yield したらダメ」程度の検証しかしてくれませんが、それと比べると少し厳しくしてくれます。

ジェネレータ関数で配列を単に生成するとき、

return; は文字通り早期リターン、つまり「配列の生成はこれで終わり」とけりをつけて終了するのに使えますが、

return 1; のように書いても、その 1 という値は特に何の利用価値もありません。むしろ yield すべきだったのに書き間違えた可能性があります。

const nums2 = generateArraynumber>(function* () {
  yield 1;
  if (isHoge) {
    return; // 
  }
  yield* [2, 3];
  return 1; // 
});

generateArray は、その型宣言によって、return; は許可しつつ、 return 1; には以下のような型エラーを提供してくれます。

() => Generator の引数を型 () => Generator のパラメーターに割り当てることはできません。

この型エラーは、引数の generatorFn について、関数の型を明示して「この関数が値を return しない」という制約を与えているから、と考えられます。

(再掲)src/utils/generateArray.ts

/**
 * 渡されたジェネレータ関数を実行して、配列にする。
 */
export const generateArray = T>(
  generatorFn: () => GeneratorT, void>,
): T[] => {
  return Array.from(generatorFn());
};
GeneratorT, void>,
// ^ 関数がジェネレータ関数であること(必要ではないが十分条件として)
//        ^ T: yield する値の型
//           ^ TReturn: void なので「値を返さない」
//               ^ 第3引数 TNext は省略 -> any

正直、僕もジェネレータの全てを理解しているわけではないので、今回使わなかった第3引数の TNext の使い所とかよくわかりませんでしたが、だれかが書いてくれていると期待して閉じます。

おしまい。





Source link

Views: 1

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -