型システム

出典: フリー百科事典『ウィキペディア(Wikipedia)』
移動: 案内検索

型システムtype system)とは、プログラミング言語において、そのなどの部分が持つを、その種類((type))に沿って分類し、プログラムが正しく振る舞うこと、といった性質について保証する手法である。型システムは、型理論に基づいており、プログラミング言語の理論において最も確立された軽量形式手法である[1]

定義[編集]

[編集]

プログラミング言語はさまざまな値を扱う。三角関数は浮動小数点数引数にとり浮動小数点数を返す。先頭の文字を大文字にする関数は文字列を引数にとり文字列を返す。ユーザーからの入力を数値として扱うためには、文字列を解釈して数値を返す関数が必要である。ここで、3.14 や "hoge" といった値について「浮動小数点数」や「文字列」といった種類に分類して扱っているが、同じ種類の値であれば同じ操作が可能である。この「値の種類」がである。

型検査[編集]

プログラムにおけるエラーはさまざまだが、型に基づく一連のエラーがある。単純な例としては、浮動小数点数を表現しているワードを(一般的なコンピュータのハードウェアでは、メモリ上のワードとしては区別がつかないため)整数型として扱ってしまう、といったようなものである。この例では 0 と +0.0 のような特別な場合を除いてたいていの場合は得られる結果は無意味であり、より複雑な構造を持った値の場合は構造を壊して不正にしてしまうかもしれない。このような異常をプログラムが起こさないことを検査するのが型検査である。

静的型付けと動的型付け[編集]

プログラムを実行せずに型検査を行うのが静的な型付け、静的型付けであり、プログラムを実行しながら型検査を行うのが動的な型付け、動的型付けである。

Javaは一般に静的型付け(静的型付き言語)であるが、ダウンキャストは明示する必要があり実行時に型検査を受ける。Common Lispは一般に動的型付け(動的型付き言語)だが、type specifierという静的に型を指定する文法も持っている。といったように、ある程度は混在している言語もある。さらに、静的検査が行われるタイミングについても、コンパイル時の他リンク時やインタプリタソースコード読み込み時といった場合もありえるため、簡単に見た目では分類できないこともある。

安全性[編集]

型にまつわるものに限らず一般に、プログラムが言語で定義していない状態(たとえばバッファオーバーランなどによる)や、言語仕様で「未定義」とされている状態(たとえばC言語の標準では、そういった「未定義」の場合が決められている)にならない、という性質。プログラムのエラーを(エラーが原因の異常動作によるセグメンテーション違反などによってではなく)ランタイムやインタプリタが検出して異常終了するような場合も「安全」の側に含まれる。型にまつわる安全性が型安全性である。

強い型付けと弱い型付け[編集]

型検査によって型安全性があるような型付けが強い型付け、無いものが弱い型付けである。たとえばMLは静的で強い型付け、Cは静的で弱い型付けである。動的な型付けの利点として、実行時のコストと引き換えに高度な解析無しに安全にできる、という点が挙げられ、多くの動的型付き言語(の処理系)は安全(強い型付け)である。動的型付けで安全でない例としては、古いTiny BASICなどで単純化のため、変数 A を数値として使っていても、同じメモリに文字列変数 A$ としてアクセスできてしまう、といったものがあった。

プログラムの安全性の定義については論者による議論もある所であり、またバッファオーバーランのように、型検査でプログラムの全ての安全性を保証できるわけでもないが、型システムによる大きな利点があることは確かである。

explicitな型付けとimplicitな型付け[編集]

Cのように型を直接具体的に書く(必要がある)型付けがexplicit(明、あるいは陽)な型付け、Haskellのように変数の使われかたなどから型推論により型付けが行われるのがimplicit(暗、あるいは陰)な型付けである。

議論[編集]

静的型付けと動的型付け[編集]

動的な型付けはスクリプト言語RAD言語などによくみられる。インタプリタ言語では動的型が採用されることが多く、コンパイラ言語では静的型が採用されることが多い。

型検査がどのように働くのかを見るために、次の擬似コードを考える。

var x;
x := 5;
x := "hi";

この例では、1行目でxという名の変数を宣言し、2行目でxに整数5を代入し、3行目でxに文字列"hi"を代入している。ほとんどの静的型付けの処理系ではこのようなコードは不正(型エラー)となる。なぜなら2行目と3行目でxに一貫性のない型の値を代入しているからである。

対照的に純粋な動的型付けの処理系では、型は変数ではなくに付けられるので、上のようなコードが実行できる。動的型付け言語の処理系は間違った文や式を実行したときに、値の誤用に関するエラーを型エラーとして捕捉する。つまり、動的型付けはエラーをプログラムの実行中に捕捉する。動的型付けの典型的な実装ではプログラム中のすべての値が型情報を持ち、演算に値を使う前に型情報を確かめる。例を挙げる。

var x := 5;
var y := "hi";
var z := x + y;

このコードでは、1行目でxに値5束縛し、2行目でyに値"hi"を束縛し、3行目でxyを足そうとしている。動的型付け言語ではxに束縛された値は(整数, 5)というペアとして表すことができ、yに束縛された値は(文字列, "hi")というペアで表すことができる。プログラムが3行目を実行しようとしたとき、処理系は整数文字列という型情報を検査し、もし演算+(加算)がその2つの型について定義されていなかったら、エラーを出す。

プログラミング言語の中には、静的に型検査されないコードをプログラマが書けてしまう「バックドア」を持つものもある。例として、JavaやC風の言語には「キャスト」がある。

プログラミング言語が静的型付けをもつことは必ずしも動的型付けをもたないことを意味するわけではない。例えばJavaは静的型付けを採用しているが、処理によっては動的な型情報の取得を必要とするものもあり、それらは動的型付けの一形態とみなせる。静的型付けと動的型付けの違いについては様々な議論がある。

性質[編集]

最適化
静的な型検査によってコンパイラは最適化に有用な情報を得ることがある。例えばある型の値が4の倍数のアドレスに配置されることが保証されていれば、コンパイラはより効率の良いマシン命令を選択できる。
可読性
より表現力の高い型システムでは型はプログラマの意図を説明することができるので、ドキュメントの役割を果たすこともある。例としてタイムスタンプが整数の派生型である環境において、プログラマが単なる整数ではなくタイムスタンプを返す関数を宣言すると、その型情報が関数の意味を記述していることになる。
抽象化またはモジュール化
型によってプログラマは低レベルでの実装に煩わされずにより高レベルで考えることができるようになる。例えば文字列型によってプログラマは文字列を文字列として、単なるバイトの列ではないものとして考えることができる。また型によってプログラマは2つのサブシステム間のインタフェースを表現することができる。これはサブシステムの相互運用性に必要な定義を局所化し、それらのサブシステムが通信する際に起きる矛盾を防止する。

バリエーション[編集]

典型的には、プログラム中ではすべての値には1つの特定の型が付けられる(1つの型が複数の派生型を持つ場合でも)。オブジェクトモジュール通信路依存関係、及び型自身にさえ型が付けられることもある。例を挙げると

データ型 
値の型
クラス 
オブジェクトの型
カインド 
型の型

などがある。

プログラミング言語ごとにある型システムは型付けされたプログラムがどのようにふるまい得るかを規定し、その規則から外れたふるまいを不正であると判定する。型システムより粒度の小さいコントロールはエフェクトシステムが提供する。

トレードオフ[編集]

静的型付けか動的型付けかの選択はいくつかのトレードオフを必要とする。

静的型付けは型エラーをコンパイル時にある程度確実に発見する。よって最終的なプログラムの信頼性を上げるはずである。しかしながら、型エラーがどれほど犯しやすい間違いなのか、その内の何割が静的型付けで検出できるのか、という点についてプログラマの意見は割れている。静的型付けの支持者は型検査されたプログラムの方が信頼性が高いと信じており、それに対して動的型付けの支持者は実際に流通しているソフトウェアの信頼性では大差ない点を指摘している。 静的型付けの価値は型システムの強度が上がるにつれて高まっていくと考えられる。MLHaskellなどの強く型付けされた言語の支持者は、プログラム中のデータ型を十分にプログラマが宣言するかコンパイラが推論すれば、ほぼすべてのバグは型エラーとみなせると提案している[2]

静的型付けは大抵、より高速に実行可能なコンパイル済みコードを生成する。コンパイラが正確なデータ型を知っていれば、最適化されたコードを生成できる。さらに、静的型付け言語のコンパイラではショートカットをみつけるのもより簡単になる。この理由からCommon Lispなどのいくつかの動的型付け言語では随意で型宣言ができるようになっている。最適化のための型付けは静的型付けの影響で普及した。

対照的に、動的型付けのほうがコンパイラやインタプリタの動作が高速になることがある。動的型付けの言語ではソースコードが変更されてもやり直すべき解析が少ないためである。これは「編集-コンパイル-テスト-デバッグ」というサイクルの時間を減らす。

型推論のない静的型付け言語(Javaなど)ではプログラマがメソッドや関数の型を宣言しなければならない。これはプログラムの追加的なドキュメントとして機能することがあり、コンパイラによってコードと同期させることが強制される。しかし型宣言のない静的型付け言語もあるので、これは静的型付けのというよりは型宣言の報酬である。

動的型付けはいくつかの静的型付けでは不正となり実現できない仕組みを可能にする。例えばデータをコードとして実行するeval関数である。さらに動的型付けでは、具体的なデータ構造の代わりに文字列を暫定的に用いることなどがやりやすく、プロトタイピングとの相性も良い。

動的型付け言語のメタプログラミング機能はより強力で使いやすいことが多い。例を挙げると、C++テンプレートRubyPythonでの等価なコードより、書くのが煩わしい。またイントロスペクションのような、より高度な実行時の仕組みを静的型付け言語で使うのは、さらに困難になることが多い。

強い型付けと弱い型付け[編集]

強い型付けの定義の1つは、ある処理・演算が間違った型の引数をとることを禁止するというものである。例えばCでは不正なキャストができるが、これは強い型付けが存在しないことを示している。コンパイルが通ってしまうというだけでなく、実行時にも許されてしまうからである。これはコンパクトで高速なCのコードを可能にするが、デバッグをさらに困難なものにする。

未定義の処理を引き起こさないことを安全性と言う(前述)。

弱い型付けとは、型検査で安全性を保証しないような型付けである。よって、たとえば、

var x := 5;
var y := "37";
x + y;

弱い型付けの言語でこのようなコードを書くと、どのような(型の)結果が得られるのか自明ではない。Visual Basicなど、実行すると42という結果を出す言語もあり、そのような処理系は演算が意味のあるものになるように文字列"37"を数値の37に変換する。またJavaScriptなど、"537"という結果を出す言語もあり、そのような処理系は数値5を文字列"5"に変換し、文字列同士を連結する。どちらの言語でも、結果の型を決定する規則では両方のオペランド(演算子の左と右の値)が考慮されている。AppleScriptなどの言語では、結果の型は左のオペランドの型のみによって決定される。

+演算子を文字列連結など加算以外の用途に使わないなど、演算子オーバーロードの使用を減らすことで、このような混乱を減らすことができる。例えば、.(ピリオド)や&(アンパサンド)を文字列連結に使う言語もある。

型の安全性[編集]

プログラミング言語の型システムを分類する3つめの方式は、型付けされた処理や型変換の安全性によるものである。計算機科学では、ある言語が誤った状態を引き起こす処理や変換を許さないとき、その言語は「型安全」(type-safe) であるという。

例を再掲する。

var x := 5;
var y := "37";
var z := x + y;

前述したように、Visual Basic などの言語では変数 z の値は42となる。この動作をプログラマが明確に把握しなくても、言語はこの結果を明確に定義しており、プログラムがクラッシュしたり、z に不定な値が代入されることはない。このような動作をする言語は型安全である。

C で同じ例を考えてみる。

int x = 5;
char y[] = "37";
char* z = x + y;

この例では zyのアドレスに5加えた先のメモリの内容を参照する。言い換えれば y が指す文字列の終端のヌル文字の2バイト先を指している。その位置のデータは不定(プログラムで定義されていない)であり、おそらくアドレス可能なメモリの外側にあるので、ここで z をデリファレンスするとプログラムの終了を引き起こすことがある。これは型付けされているが、メモリセーフではないプログラムの例である。型安全な言語ではこのような状況は起こりえない。

ポリモーフィズムと型[編集]

ポリモーフィズム(多相)という語は、コード(具体的には関数やクラス)が複数の型の値に基づいて動作できること、または同じデータ構造の異なるインスタンスが異なった型の要素を持てることを指す。型システムによってはコードの再利用性を改善するためにポリモーフィズムを持つものもある。ポリモーフィズムのある言語ではプログラマはリスト連想配列のようなデータ構造を使用される要素の型ごとにではなく、単に一度だけ実装すればよい。この理由から計算機科学ではこの種のポリモーフィズムの利用はジェネリックプログラミングと呼ばれることがある。ポリモーフィズムの型理論における基礎は抽象化モジュール、また(場合によっては)派生型についての研究と密接な関連がある。

ダック・タイピング[編集]

プログラミング環境によっては、2つのオブジェクトの間に全く関係がなくともそれらが同じ型を持つことがある。例としてはC++のポインタとiteratorの双対性が挙げられる。両方とも * 演算が定義されているが、全く別のメカニズムによって実装されている。

このテクニックは「ダック・タイピングDuck Typing)」と呼ばれる。由来は次の警句である。

"If it waddles like a duck, and quacks like a duck, it's a duck!"

明示的または暗黙的な型の宣言と型推論[編集]

多くの静的な型システム(例えば C や Java)は型宣言を必要とし、プログラマはすべての変数に特定の型を明示的に関連付けなければならない。そうでないもの(例えば Haskell)は型推論を行い、コンパイラはプログラマの変数に対する扱いから変数の型についての結論を引き出す。例として、xy を足す関数 f(x, y) を仮定すると、コンパイラは加算は数値のみに定義されているので xy は共に数値であると推論できる。ゆえにプログラム中で f の引数として数値でない型(文字列やリストなど)を取って呼び出すとエラーを報告する。

コード中の数値や文字列のリテラルおよび式は型を暗示する。例えば、式 3.14 はおそらく浮動小数点数を暗示し、式 1, 2, 3 はおそらく整数のリスト(主に配列)を暗示する。

互換性(等価性と派生型)[編集]

静的型付け言語の型検査では、すべての式の型がその式が現れた文脈で期待される型と一貫しているか、検証しなければならない。たとえば、x := eという代入文では式eの推論される型は変数xの宣言型または推論型と一貫していなければならない。この一貫性の概念を互換性といい、プログラミング言語ごとに固有のものである。

明らかなように、exの型が同一でかつその型への代入操作が許可されているなら、これは正当な式である。したがって最も単純な型システムでは、2つの型が互換であるかどうかは2つの型が同一である(または等価である)かどうか(等価性)という単純な問題に置き換えることができる。別の言語では2つの式が同じ型を持つと理解されるのに別の基準を採用している。これら、型の同一性理論は非常に多岐にわたっており、2つの極端な例は structural typingnominative typing である。structural typing とは同じ外部構造(インタフェース、特に暗黙的なもの)を持つ値を同じ型であるとするもので、nominative typing とは型宣言の構文からのみ型の同一性を判定する(型が同じ「名前」を持たなければならない)ものである。

派生型のある言語では互換性の関係はより複雑なものとなる。型Aが型Bの派生型であるとすると、A型の値はB型の値が必要とされる文脈で使用することができる。しかし逆は真ではない。等価性と同様に派生型の関係はプログラミング言語によって異なった方法で定義され、多くのバリエーションが存在しうる。パラメータ付けされたまたはアドホックなポリモーフィズムを持つ言語の存在は型の互換性に何らかの意味を持つのかもしれない。

論争[編集]

静的および強い型付けの支持者と動的および自由な型付けの支持者の間では衝突が度々おきる。前者のグループは厳密な型付けの使用によって、処理系がより多くのエラーを問題が大きくなる前に発見できるようになると主張している。後者のグループはより気軽な型付けによってコードはよりシンプルなものになり、そのようなコードは解析しやすいとされるので、エラーは減少すると主張している。型推論がある言語では型を手で宣言する必要はほとんどないので、強い型付けに伴う開発のオーバーヘッドは低減される。

個人がどのグループに分かれるかは、開発しているソフトウェアの種類やチームのメンバーの能力、他のシステムとの対話性の度合い、開発チームの規模などに依ることが多い。少人数で小回りのきくプロジェクトには気軽な型付けがより合い、フォーマルで大人数で仕事が分断されている(プログラマ、アナリスト、テスト部隊、など)プロジェクトは厳密な型付けのほうがうまくいくことが多い、と結論づける者もいる。

脚注[編集]

[ヘルプ]
  1. ^ 『型システム入門』 p. 1
  2. ^ http://citeseer.ist.psu.edu/xi98dependent.html

参考資料[編集]

関連項目[編集]