タプルの残余要素
タプルと残余引数の組み合わせで登場した...演算子に関して、これをタプルの定義内で利用することも可能です。つまり、残余要素です。最後に、この残余要素を紹介していきます。
残余要素の基本
残余要素が定義されたタプルの例としては、リスト10のようなものがあります。
type PersonalData = [string, number, number, ...string[]]; // (1) const taro: PersonalData = ["田中太郎", 170.2, 74.8]; // (2) const jiro: PersonalData = ["鈴木二郎", 170.2, 74.8, "こんにちは"]; // (3) const saburo: PersonalData = ["山本三郎", 170.2, 74.8, "こんにちは", "さようなら", "またあした"]; // (4)
リスト10の(1)のPersonalDataの最後の要素が、「...string[]」と、文字列配列に対して...が適用されています。このように定義すると、第3要素までは定義通りのデータを記述する必要がありますが、第4要素以降に関しては、文字列を複数定義できますし、定義しなくてもかまいません。実際、リスト10の(2)の第4要素を定義していないパターン、(3)の1個のパターン、(4)の3個のパターンの全てがエラーなく実行できます。
残余要素とジェネリクスの組合せ
この残余要素に対して、ジェネリクスによる型指定が、バージョン3.0で可能となりました。例えば、リスト11のようなコードです。
function show<T>(personalData: [string, number, number, ...T[]]) { // (1) : } show<string>(["田中太郎", 170.2, 74.8, "こんにちは", "さようなら", "またあした"]); // (2) show<number>(["鈴木二郎", 170.2, 74.8, 51, 52, 53]); // (3)
リスト11の(1)の関数show()では、残余要素のデータ型をTとしており、その型をジェネリクス(<T>)で指定できるようになっています。この関数を利用する場合は、(2)や(3)のようになります。(2)ではジェネリクスとしてstringを指定しているので、引数タプルの第4要素以降は文字列となっています。一方、(3)では、numberをジェネリクスとして型指定しているので、第4要素以降は数値となっています。
残余要素を任意の要素に定義可能に
当初、この残余要素に関しては、必ずタプル定義の末尾の要素として定義する必要がありました。「残余(Rest)」という命名からは、当たり前といえば当たり前ですが、バージョン4.2で任意の要素に定義できるようになりました。例えば、リスト12のようなコードです。
type PersonalData = [string, number, ...string[], number]; // (1) const taro: PersonalData = ["田中太郎", 170.2, 74.8]; // (2) const jiro: PersonalData = ["鈴木二郎", 170.2, "こんにちは", 74.8]; // (3) const saburo: PersonalData = ["山本三郎", 170.2, "こんにちは", "さようなら", "またあした", 74.8]; // (4)
リスト12の(1)のPersonalDataでは、残余要素を第3要素として定義しています。そのため、(2)の数値と数値の間に文字列が全くない状態は当然として、(3)や(4)のように、数値と数値の間に任意の文字列を指定できるようになっています(もちろん、この定義が可読性の観点からよいかどうかは別として)。
このように任意の位置に定義できるようになった残余要素ですが、制約が2点あります。まず、残余要素は必ずひとつのタプルにひとつしか定義できないということです。そのため、次のようなコードはエラーとなります。
type PersonalData = [...string[], number, ...string[], number];
もうひとつの制約は、残余要素とオプション要素は混在できない、ということです。そのため、次のようなコードはエラーとなります。
type PersonalData = [...string[], number, string, number?];
ラベル付きタプルとラベルなしタプルの混在
このような残余要素を含むタプルに対して、もちろんラベルを付けることもできます。例えば、リスト13のような定義コードです。
type PersonalData = [name: string, height: number, weight: number, ...msgs: string[]];
ここでの注意点は、...をラベル(名称)の方につけ、型指定はstring[]のように単なる配列として定義することです。
このように定義したタプルを生成する場合、もちろん、リスト10の(2)〜(4)のように、taro、jiro、saburoは問題なく定義できます。
ところで、このような残余要素を含むタプルに対してラベルを付与しようとした場合、残余要素という性質上、ラベルがそもそもつけづらい要素の場合も出てきます。このラベル付きタプルがバージョン4.0で導入されて以来、実は、ラベルを利用する場合は全ての要素につける必要がありました。すなわち、ラベルのついている要素とついていない要素を混在させることができませんでした。そこで、名称が決めづらい残余要素の場合は、例えばrestやothersのように、当たり障りのないラベルを採用していました。
これが、バージョン5.2でラベルのある要素とラベルのない要素が混在できるようになりました。そのため、リスト13は、リスト14のような定義でも問題なく動作するようになっています。
type PersonalData = [name: string, height: number, weight: number, ...string[]];
まとめ
TypeScriptのバージョン5.2までに導入された新機能をテーマごとに紹介する本連載の第2回目はいかがでしたでしょうか。
今回は、タプルに関するアップデートを紹介しました。具体的には、読み取り専用やラベル付きタプル、さらに残余引数との組合せ、残余要素に関しての新機能を紹介しました。次回は、型にまつわる新しい仕組みを紹介します。