皆さんこんにちは。TypeScript 5.6で追加されたIterator Helpersのサポートについては以下の記事で紹介しました。
しかし、上記の記事はTS 5.6 Beta時点のもので、その中で説明した状態から実装が変更されました。記事のタイトルにある BuiltinIterators
も別の名前に変わりました。
そこで、この記事ではTS 5.6の正式版でどうなったかについて改めて説明します。
ちなみに、この変更は私がTypeScriptのGitHubリポジトリに変更を提案するissueを建てたことで行われました。(TSチーム内で元々議論があったかもしれないのでうぬぼれかもしれませんが)
Iterator Helpersとは
前回の記事でも紹介したので軽く流しますが、Iterator HelpersはECMAScriptの新機能であり、従来機能が乏しかったイテレータにメソッドをたくさん追加するものです。次の例では、イテレータに追加されたfilter
メソッドを使用しています。
/**
* 与えられたSet<number>のうち、偶数のみを出力するイテレータを返す
*/
function iterateEvenNumbers(
set: Set<number>
) {
return set.values().filter(n => n % 2 === 0);
}
const nums = new Set([1, 2, 3, 4, 5]);
for (const v of iterateEvenNumbers(nums)) {
console.log(v); // 2, 4が出力される
}
IteratorObject
TypeScript 5.6ではIteratorObject
という型が追加されました。TS 5.6 BetaでBuiltinIterator
と呼ばれていたものが改名されました。
IteratorObject
は「Iterator Helpersを持つイテレータオブジェクト」を表す型です。言い換えれば、ランタイムにIterator.prototype
をプロトタイプに持つオブジェクトだと思っても差し支えないでしょう。
つまり、上の例で使われているfilter
メソッドはIteratorObject
型に由来するメソッドです。上の例をPlaygroundで調べてみましょう。filter
にマウスを乗せると次のように表示され、filter
がIteratorObject
のメソッドであることが確かめられます。
(method) IteratorObject<number, undefined, unknown>.filter(predicate: (value: number, index: number) => unknown): IteratorObject<number, undefined, unknown> (+1 overload)
TypeScriptでは「Foo
というクラスのインスタンスであるオブジェクトの型はFoo
」とするのが原則ですが、ここでは「Iterator
というクラスのインスタンスであるオブジェクトの型はIteratorObject
」となっており原則から外れています。これは前回の記事で説明した通り、Iterator
型はすでに別の意味でTypeScriptに存在していたからです。
ビルトインメソッドが返すイテレータの型
TS 5.6 Betaから正式版までに変わった点として、ビルトインメソッドが返すイテレータの型の表現があります。具体的には、Setのvalues
メソッドの返り値の型などです。
const nums = new Set([1, 2, 3, 4, 5]);
const values = nums.values();
// ^?
これは以下のように変遷しました。
// TS 5.5
IterableIterator<number>
// TS 5.6 Beta
BuiltinIterator<number, BuiltinIteratorReturn, any>
// TS 5.6
SetIterator<number>
TS 5.6では、どのビルトインメソッドから返されたのかに対応した型が用意されるようになりました。この例では、Setのメソッドが返したイテレータだからSetIterator
です。ECMAScriptの範囲では、他にもArrayIterator
, MapIterator
, StringIterator
, RegExpStringIterator
, SegmentIterator
が存在します。DOM由来のイテレータの型もたくさん定義されました。詳しくは実装のPRを参照してください。
新しく作られたこれらの型は、次のような定義になっています。(型定義ファイルから引用)
interface SetIterator<T> extends IteratorObject<T, BuiltinIteratorReturn, unknown> {
[Symbol.iterator](): SetIterator<T>;
}
ポイントは、IteratorObject<T, BuiltinIteratorReturn, unknown>
のところです。
BuiltinIteratorReturn
はどうなった
BuiltinIteratorReturn
についても前回の記事で解説しました。この型は、TS 5.6で追加されたstrictBuiltinIteratorReturn
コンパイラオプションが有効化どうかによって中身が変わります。有効時はunknown
、無効時はany
です。
元々Iterator
の第2型引数(TReturn
)のデフォルト値がany
になっている問題がありました。せめてビルトインのイテレータ(ビルトインメソッドが返す、今でいうIteratorObject
に相当する型)ではTReturn
がunknown
になるように改善したいというモチベーションで作られたものです。一応後方互換性の問題があるため、コンパイラオプションとセットで導入となっています。
上のような定義になっているため、SetIterator<T>
などではTReturn
はBuiltinIteratorReturn
になります。
この辺りの型定義の取り扱いが、TS 5.6 Betaから正式版の間に特に改善されたところです。
Setのfilter
メソッドの定義をTS 5.6 Betaと正式版で見比べてみましょう。
// TS 5.6 Beta
values(): BuiltinIterator<T, BuiltinIteratorReturn>;
// TS 5.6
values(): SetIterator<T>;
このように、5.6 Betaの時点では、BuiltinIterator
(今でいうIteratorObject
)の第2型引数をBuiltinIteratorReturn
に指定しており、BuiltinIterator
を返す全箇所で明示的にBuiltinIteratorReturn
と書く定義になっていました。
筆者が建てたissueで指摘していた問題は、BuiltinIterator
の第2型引数のデフォルト値がany
のままであり、型定義を明記するときにうっかりBuiltinIterator<T>
と書いてしまうとBuiltinIteratorReturn
が使われなくなってしまう(せっかくstrictBuiltinIteratorReturn
オプションが有効になっていてもその恩恵が消えてしまう)ということです。
TS 5.6の正式版では型はSetIterator<T>
のようにBuiltinIteratorReturn
が表面には出てこない定義になっており、この問題が解消されています。
ちなみに、筆者は最初BuiltinIterator
の第2型引数TReturn
のデフォルト値をBuiltinIteratorReturn
にしてしまえばいいのでは? という提案をしたのですが、それはできませんでした。それは、BuiltinIteratorReturn
はジェネレータ関数の型推論にも関わっており、その場合第2型引数がvoid
となりBuiltinIteratorReturn
とかみ合わないからです。
TS 5.6の最終的な型定義では、ビルトインのメソッドが返すイテレータに個別の型名を与えることで、BuiltinIteratorReturn
を型引数のデフォルトにしないことと、型名の表面に現れないことを両立しています。
まとめ
TS 5.6のIterator Helpersサポートは待望の機能で、イテレータの利便性を大きく向上してくれます。
最終的な形になるまでには、この記事で説明したような試行錯誤(?)と変遷がありました。
特に、Beta時点でBuiltinIterator
型だったものはIteratorObject
型に改名され、ビルトインのメソッドが返すイテレータの型定義はIteratorObject
型を直接使うのではなく、より具体的な型を介する定義に変更されました。