静的型付け
プログラミング言語は大きく2つに分類されます。静的型付け言語と動的型付け言語です。静的型付け言語の中には、C言語やJavaがあり、今学んでいるTypeScriptもその仲間です。一方の動的型付け言語には、RubyやPython、PHP、そしてJavaScriptがあります。
もし、読者のあなたが動的型付け言語をずっとやってきて、TypeScriptが初めての静的型付け言語になるなら、きっとこのような疑問を持つはずです。
- 「動的型付け言語」と「静的型付け言語」にはどんな違いがあるか?
- 「静的型付け言語」にはどんな利点があるのか?
ここでは、そんな静的型付け言語に初めて挑戦する読者の疑問を解消していきます。
#
動的型付け言語は「実行時」に型が定まる静的型付け言語とは何かを考える前に、慣れ親しんだ動的型付け言語とは何だったのか振り返ってみましょう。
たとえばPythonで、与えられた引数から1を引き算して返す関数を例に考えてみます。
python
def minus(x):return x - 1print(minus(2))
python
def minus(x):return x - 1print(minus(2))
このminus()
関数には変数x
があり、ここに整数型の2
が代入されると問題なく計算され、整数型の1
が返ります。当たり前ですね。
では、今度はminus()
関数にstring
型の"two"
を渡すとどうなるでしょうか。
python
def minus(x):return x - 1print(minus("two"))
python
def minus(x):return x - 1print(minus("two"))
このコードを実行してみると、「TypeError: unsupported operand type(s) for -: 'str' and 'int'」というエラーが発生します。これは「文字列から整数はマイナスできないよ」という意味です。
文字列から数値を引き算するというのは、人間からすると明らかに変です。実行するまでもなく型のおかしさに気づくと思います。
しかし、動的型付け言語ではプログラムを実行するまで型の問題は見過ごされます。プログラムを実行していくと、変数の型が定まっていきます。その過程で問題が発覚して、エラーになるのです。
ここからわかるように、動的型付け言語の最大の特徴は、変数の型はプログラムを実行するまで定まらないという点です。この特徴はプログラミングの自由度を高めるという利点でもあります。
#
静的型付け言語は「コンパイル時」に型が定まるでは、静的型付け言語にはどのような特徴があるのでしょうか?先ほどPythonで示したminus()
関数を静的型付け言語であるTypeScriptで書いてみましょう。
typescript
function minus(x: number) {return x - 1;}
typescript
function minus(x: number) {return x - 1;}
注目すべきは、変数x
の隣にnumber
と書かれている点です。これは、型注釈と呼ばれるもので、変数x
がnumber
型であることを表しています。
この変数x
にstring
型の"two"
を代入してコンパイルしてみるとどうなるでしょうか?
typescript
minus("two");
typescript
minus("two");
コンパイル結果はエラーになり、「Argument of type 'string' is not assignable to parameter of type 'number'.」という警告が表示されます。この警告の内容は「number
型のパラメータにstring
型は代入できません」というものです。
Pythonの例でも似たようなエラーが確認できましたが、それは実行時に発生していました。一方、このTypeScriptの例のエラーは、コンパイル時に発生しています。ここが動的型付け言語と静的型付け言語の大きな特徴の違いです。コンパイル時というのは、プログラムを実行するよりも前の工程です。つまり、型が実行時よりも前のコンパイル時で定まるというのが、静的型付け言語の特徴なのです。そのおかげで、明らかにおかしな代入を早期発見できるというのが、TypeScriptを始めとした静的型付け言語の大きな利点になるわけです。
#
型注釈をコストと考えるか、投資と考えるか動的型付け言語では書く必要がなかった型注釈を、TypeScriptでは基本的には変数ごとに書いていくことになります。そのため、動的型の言語からTypeScriptに入ってきた人に中は、コードの記述量が増えて面倒に感じる人がいるかもしれません。型注釈の記述は「コスト」と考えることもできます。(それを省力化する「型推論」という仕組みもありますが、ここではその説明は割愛します。)
そもそも型注釈の目的は何なんでしょうか?それは、プログラマがコンパイラに「この変数の型は何であるか」を教えることです。型注釈があると、たとえば、コンパイラがminus()
関数を処理するときに、「x
変数はnumber
型でなければならないのか。じゃあ、string
型を代入するようなコードは間違いだな。エラーにしてプログラマに教えてあげよう」といったような働きをしてくれるのです。
コンパイラは、細かく問題を指摘してくるので、鬱陶しく思うかもしれません。ここは見方を変える必要があります。コンパイラは「あなた専属のコードレビュア」なのだと。バグに気づかずリリースしてしまい、大問題となって発覚するといった経験はないでしょうか?コンパイラはそういったことに至るずっと前の段階で、問題点を教えてくれる頼りになる存在です。
コンパイラも決して全知全能ではありません。ちゃんと型を教えてあげなければ、いいコンパイラに成長しません。逆に型注釈をちゃんとしていけば、多くの問題に気がつく賢いコンパイラに育っていきます。この観点に立つと、型注釈はコストではなく、コンパイラを正しく育て、バグを未然に防ぐための「投資」と考えることができるのではないでしょうか。
#
まとめ- 動的型付け言語: 実行時に変数の型が定まる言語。型にまつわる問題はプログラムを実行してみないと発覚しない。
- 静的型付け言語: コンパイル時に変数の型が定まる言語。型にまつわる問題はプログラムを実行しなくても発見できる。
- 型注釈は、あなた専属のコードレビュアであるコンパイラを育てるための投資。