フリーダムの日記

GIS(地理情報システム)を中心に技術的なことを書いています。

プログラミング TypeScript:第 2 章 TypeScript 全体像

はじめに

TypeScript の学習のために「プログラミング TypeScript ―スケールする JavaScript アプリケーション開発」を購入しましたので、自身の学習のためにも本プログで数回に渡って、重要な部分に絞って紹介していければと思います。

プログラミングTypeScript ―スケールするJavaScriptアプリケーション開発

プログラミングTypeScript ―スケールするJavaScriptアプリケーション開発

  • 作者:Boris Cherny
  • 発売日: 2020/03/16
  • メディア: 単行本(ソフトカバー)

学習用にサンプルプログラミングも Github で紹介されていますので、合わせて紹介していきます。 github.com

TypeScript:全体像

TypeScript 言語について紹介し、TypeScript コンパイラー (TSC:TypeScript Compiler) がどのように動作するかの概要を説明し、TypeScript の型システムを中心に説明していきます。

その他、TypeScript の開発環境のセットアップや tsconfig.jsonコンパイルについて前回の記事を参考にしてください。 freedom-tech.hatenablog.com

TypeScript 本家サイト www.typescriptlang.org

TypeScript について (Wiki) ja.wikipedia.org

コンパイラ

TypeScript のコンパイルは独特で、バイトコードへと直接コンパイルする代わりに、JavaScript コードへとコンパイルします。その JavaScript コードを通常通の方法で、ブラウザ内で、または、Node.js で実行します。作成したプログラムに対して TypeScript コンパイラー (TSC) が AST を生成した後で、(JavaScript コードを出力する前に) TSC はコードの型チェック (typecheck) を行います。

型チェックと JavaScript コードの出力を含めると、TypeScript のコンパイルプロセスは、大雑把に以下のようになります。

TS

  1. TypeScript ソース → TypeScript AST
  2. AST が型チェッカーによってチェックされる
  3. TypeScript AST → JavaScript ソース

JS

  1. JavaScript ソース → JavaScript AST
  2. AST → バイトコード
  3. バイトコードがランタイムによって評価される

1 から 3 のステップは TSC によって行われ、4 から 6 のステップは JavaScript ランタイムによって行われます。JavaScript ランタイムは、ブラウザー内、Node.js 内、あるいは使用しているどの JavaScript エンジン内にも存在しています。

型システム

モダンなプログラム言語は、さまざまな型システムを備えています。
型システムには、一般的に2つの種類があります。1つは、明示的な構文を使って、すべてのものの型をコンパイラーに伝える必要があります。もう1つは、型を自動的に推論するものです。どちらのアプローチにもトレードオフがあります。
TypeScript は、両方の種類の型システムから発想を得ています。型に明示的にアノテーションを付けることもできますし、ほとんどの型について、あなたの代わりに TypeScript に推論させることもできます。 型が何であるかを明示的に TypeScript に伝えるには、アノテーション (annotation) を使います。アノテーションは、「値:型」という形式を取り、型チェッカーに「ここに値が見えるだろう?その横にあるのが、その値の型だよ」と伝えます。
以下に例を示します。

let a: number = 1    // a は number です
let b: string = 'hello'  // b は string です
let c: boolean[] = [true, false] // c は boolean の配列です

あなたの代わりに TypeScript に型を推論させたければ、単に型を省略し、TypeScript に仕事をさせます。

let a = 1     // a は number です
let b = 'hello'    // b は string です
let c = [true, false]   // c は boolean の配列です

TypeScript が型を推論するのが上手であることが分かるかと思います。アノテーションを省略しても、型は同じです。

一般に、明示的に型付けされたコードの使用を最小限に抑え、プログラマーの代わりに TypeScript にできるだけ多くの型を推論させるのは、良いスタイルと言えます。

TypeScript 対 JavaScript

TypeScript の型システムについて、JavaScript の型システムと比較して、さらに詳しく見ていきましょう。 以下の表に概要を示します。

型システムの特徴 JavaScript TypeScript
型はどのようにバインドされるのか? 動的 静的
型は自動的に変換されるのか? はい いいえ (多くの場合)
型はいつチェックされるのか? 実行時 コンパイル
エラーはいつ表面化されるのか? 実行時 (多くの場合) コンパイル時 (多くの場合)

型はどのようにバインドされるか?

「型が動的にバインドされる」とは、JavaScript がプログラム内の型を知るために、実際にそのプログラムを実行しなければならないことを意味します。JavaScript は、プログラムを実行するまで、型について知ることはありません。TypeScript は、漸進的型付き言語 (gradually typed language) です。これは、コンパイル時にプログラム内のすべてのものの型がわかっている必要はないことを意味します。型付けされていないプログラムでさえ、TypeScript はいくつかの型を推論し、いくつかのミスを見つけることができます。

型は自動的に変換されるのか?

JavaScript は、弱く型づけされた言語です。これは、数値と配列を足し合わせるような不正なことをした場合、プログラマーから与えられたものを使って最善の結果が得られるように一連のルールを適用してプログラマーの本当の意図を理解しようとすることを意味します。ここで、3 + [1] を JavaScript がどのように評価するかという具体的な例を見てみましょう。

  1. JavaScript に 3 が数値であり、[1] が配列であることを理解します。
  2. + を使っているので、その2つを連携したいのだろうと推定します。
  3. 3 を暗黙に文字列に変換し、"3" とします。
  4. [1]を暗黙に文字列に変換し、"1" とします。
  5. それらの結果を連結し、"31" とします。

これを明示的に行うこともできます。

3 + [1];           // "31" と評価されます  
(3).toString() + [1].toString();  //  "31" と評価されます

JavaScript は、プログラマーの代わりに賢い型変換を行うことで役に立とうとしますが、それに対して TypeScript は、プログラマーが何が不正なことをすると、すぐにエラーをだします。この同じ JavaScript コードを TSC にかけると、次のようなエラーになります。

3 + [1];           //  エラー TS2365: 演算子 '+' を型 "3" および   
               //  'number[]' に適用することはできません。   
(3).toString() + [1].toString();  //  "31" と評価されます

正しそうに見えないことをすると、TypeScript はエラーを出し、プログラマーが自分の意図をはっきりさせると、TypeScript は手を引きます。この振る舞いは理にかなっています。

型は自動的に変換されるのか?

JavaScript は多くの場所で、プログラマーが与えた型を重視せず、それを JavaScript が期待する型に変換するように最善を尽します。 これに対して TypeScript は、コンパイル時にコードの型チェックを行うので、前の例のようなエラーを確かめるためにコードを実行する必要はありません。TypeScript は、このようなエラーを見つけるために「静的に解析」し、プログラマーが実行する前にエラーを提示します。コードが正常にコンパイルできない場合、それはあなたがミスをしているという証拠であり、コードを実行する前にそれを修正する必要があります。

エラーはいつ表面化するか?

JavaScript が例外をスローしたり暗黙の型変換を行ったりする場合、それは実行時に行われます。つまり、あなたが何か不正なことをしたという有益な警告を得るためには、プログラムを実行する必要があるということです。最善のケースでは、それは単体テストの一部を意味するでしょうし、最悪のケースでは、ユーザーからの怒りのメールを意味するでしょう。
TypeScript は、構文に関するエラーと型に関するエラーをコンパイル時にスローします。実際には、それらのエラーはコードエディターの中で、入力するとすぐに表示されます。インクリメンタルにコンパイルされる静的型付き言語をこれまで使ったことがない人にとっては、驚くべき体験でしょう。

コンパイル時に TypeScript が発見できないエラー(スタックオーバーフロー、切断されたネットワーク接続、不正な形式のユーザー入力などもたくさんあり、それらは、実行時例外エラーという結果に終わります。TypeScript が行うは、純粋な JavaScript の世界では実行時エラーとなっていたであろう多くのエラーの中からコンパイル時にエラーを作り出すことです)もあります。

練習問題

次のコードを入力します。

let a = 1 + 2
let b = a + 3
let c = {
    apple: a,
    banana: b
}
let d = c.apple * 4

ここで、a、b、c、d の上にマウスポインターを置き、 TypeScript がすべての変数の型をどのように推論するか確認してください。a は number、b も number、c は特定の形状を持つオブジェクト、d は number です (図1)。

f:id:freedom0625:20200621211618p:plain:w350
図1. TypeScript による型の推論

常に簡単なサンプルですが、GitHub にもアップしました。 github.com

さいごに

実に内容が濃い本で大変満足しています。第2章でもそれなりのボリュームでしたので、今後はなるべく要点を絞って紹介していければと思います。