タプル (tuple)
TypeScriptの関数は1値のみ返却可能です。戻り値に複数の値を返したい時に、配列に返したいすべての値を入れて返すことがあります。なお次の関数の戻り値は定数になっていますが、実際は演算した結果だと解釈してください。
ts
functiontuple () {//...return [1, "ok", true];}
ts
functiontuple () {//...return [1, "ok", true];}
配列が抱える問題
上記例では戻り値の型として何が妥当でしょうか。配列のページから読み進めていただいた方はなんでも入れられる型、ということでany[]
またはunknown[]
が型の候補として思い浮かぶ人もいるかと思います。
ts
constlist : unknown[] =tuple ();Object is of type 'unknown'.2571Object is of type 'unknown'.list [0].toString ();
ts
constlist : unknown[] =tuple ();Object is of type 'unknown'.2571Object is of type 'unknown'.list [0].toString ();
ですが、このlist[n]
からメソッドを呼ぶことができません。それはlist
の各要素はunknown
であるからです。
ではany[]
を戻り値の型として使うべきかというと、それも問題です。せっかくTypeScriptを使って型による恩恵を享受しているのに、ここだけ型がないものとしてコーディングをするのも味気がありません。そこで使えるのがタプルです。
タプルの型
タプルの型は簡単で[]
を書いて中に型を書くだけです。つまり、上記関数tuple()
は次のような戻り値を持っていると言えます。
ts
constlist : [number, string, boolean] =tuple ();
ts
constlist : [number, string, boolean] =tuple ();
同様に関数の戻り値にも書くことができます。
ts
functiontuple (): [number, string, boolean] {//...return [1, "ok", true];}
ts
functiontuple (): [number, string, boolean] {//...return [1, "ok", true];}
配列の型はT[]
とArray<T>
のふたつの書き方がありましたがタプルはこの書き方しか存在しません。
タプルへのアクセス
タプルを受けた変数はそのまま中の型が持っているプロパティ、メソッドを使用できます。
ts
constlist : [number, string, boolean] =tuple ();list [0].toExponential ();list [1].length ;list [2].valueOf ();
ts
constlist : [number, string, boolean] =tuple ();list [0].toExponential ();list [1].length ;list [2].valueOf ();
タプルを受けた変数は、タプルで定義した範囲外の要素に対してアクセスができません。
ts
constlist : [number, string, boolean] =tuple ();Tuple type '[number, string, boolean]' of length '3' has no element at index '5'.2493Tuple type '[number, string, boolean]' of length '3' has no element at index '5'.list [5 ];
ts
constlist : [number, string, boolean] =tuple ();Tuple type '[number, string, boolean]' of length '3' has no element at index '5'.2493Tuple type '[number, string, boolean]' of length '3' has no element at index '5'.list [5 ];
そのためlist.push()
のような配列の要素を増やす操作をしてもその要素を使うことはできません。
分割代入を使ってタプルにアクセスする
上記関数tuple()
の戻り値は分割代入を使うと次のように受けることができます。
ts
const [num ,str ,bool ]: [number, string, boolean] =tuple ();
ts
const [num ,str ,bool ]: [number, string, boolean] =tuple ();
また、特定の戻り値だけが必要である場合は変数名を書かず,
だけを書きます。
ts
const [, ,bool ]: [number, string, boolean] =tuple ();
ts
const [, ,bool ]: [number, string, boolean] =tuple ();
タプルを使う場面
TypeScriptで非同期プログラミングをする時に、時間のかかる処理を直列ではなく並列で行いたい時があります。そのときTypeScriptではPromise.all()
というものを使用します。このときタプルが役に立ちます。
Promise
についての詳しい説明は本書に専門の頁がありますので譲ります。ここではPromise<T>
という型の変数はawait
をその前につけるとT
が取り出せることだけ覚えておいてください。また、このT
をジェネリクスと言いますが、こちらも専門の頁があるので譲ります。
📄️ 非同期処理
もしJavaScriptで本格的に何かを作りたいのであれば、非同期処理とは切っても切れない関係になるでしょう。初めのうちは理解に苦しむことが多いですが今では非同期処理を直観的に操作できる機能が実装されたのでハードルは大きく下がりました。
📄️ ジェネリクス
型の安全性とコードの共通化の両立は難しいものです。あらゆる型で同じコードを使おうとすると、型の安全性が犠牲になります。逆に、型の安全性を重視しようとすると、同じようなコードを量産する必要が出てコードの共通化が達成しづらくなります。こうした問題を解決するために導入された言語機能がジェネリクスです。ジェネリクスを用いると、型の安全性とコードの共通化を両立することができます。
ts
constpromise :Promise <number> =yyAsync ();constnum : number = awaitpromise ;
ts
constpromise :Promise <number> =yyAsync ();constnum : number = awaitpromise ;
たとえば次のような処理に時間が3秒、5秒かかる関数takes3Seconds(), takes5Seconds()
があるとします。
ts
async functiontakes3Seconds ():Promise <string> {// ...return "finished!";}async functiontakes5Seconds ():Promise <number> {// ...return -1;}
ts
async functiontakes3Seconds ():Promise <string> {// ...return "finished!";}async functiontakes5Seconds ():Promise <number> {// ...return -1;}
この関数をそのまま実行すると3 + 5 = 8秒かかってしまいます。
ts
conststr : string = awaittakes3Seconds ();constnum : number = awaittakes5Seconds ();
ts
conststr : string = awaittakes3Seconds ();constnum : number = awaittakes5Seconds ();
これをPromise.all()
を使うことで次のように書くことができます。このときかかる時間は関数の中でもっとも時間がかかる関数、つまり5秒です。
ts
consttuple : [string, number] = awaitPromise .all ([takes3Seconds (),takes5Seconds (),]);
ts
consttuple : [string, number] = awaitPromise .all ([takes3Seconds (),takes5Seconds (),]);
このときPromise.all()
の戻り値を受けた変数tuple
は[string, number]
です。実行する関数のPromise<T>
のジェネリクスの部分とタプルの型の順番は一致します。つまり次のように入れ替えたら、入れ変えた結果のタプルである[number, string]
が得られます。
ts
consttuple : [number, string] = awaitPromise .all ([takes5Seconds (),takes3Seconds (),]);
ts
consttuple : [number, string] = awaitPromise .all ([takes5Seconds (),takes3Seconds (),]);
Promise.all()
は先に終了した関数から順番に戻り値のタプルとして格納されることはなく、元々の順番を保持します。take3seconds()
の方が早く終わるから、先にタプルに格納されるということはなく、引数に渡した順番のとおりにタプルtuple
の要素の型は決まります。