web-dev-qa-db-ja.com

型システムとは何ですか?

バックグラウンド

サイドプロジェクトとして言語をデザインしています。動作するアセンブラー、静的アナライザー、およびそのための仮想マシンがあります。構築したインフラストラクチャを使用して、自明ではないプログラムを既にコンパイルして実行できるので、大学でプレゼンテーションを行うことを考えました。

私の話の中で、VMは型システムを提供します、「あなたの型システムは何ですか?」と尋ねられました。答えた後、私は人に笑われました質問をします。

したがって、私がこの質問をすることで評判を失うことはほぼ確実ですが、プログラマに頼ります。

私の理解

私が理解しているように、型システムはプログラム内のエンティティに関する追加の情報層を提供するために使用され、ランタイム、コンパイラ、またはその他の機械が、それが操作するビットの文字列をどう処理するかを認識します。また、コントラクトの維持にも役立ちます。コンパイラー(またはコードアナライザー、ランタイム、またはその他のプログラム)は、任意の時点で、プログラマーが期待する値でプログラムが動作することを確認できます。

タイプは、人間のプログラマーに情報を提供するためにも使用できます。たとえば、私はこの宣言を見つけます:

function sqrt(double n) -> double;

これよりも便利

sqrt(n)

前者は多くの情報を提供します:sqrt識別子は関数であり、単一のdoubleを入力として受け取り、別のdoubleを出力として生成します。後者は、おそらく単一のパラメーターを取る関数であることを示しています。

私の答え

それで、「あなたのタイプシステムは何のためですか?」私は次のように答えました:

型システムは動的です(型は値を保持する変数ではなく、値に割り当てられます)が、強制変換ルールは驚くべきものではありません(互換性のない型を表すため、整数に文字列を追加することはできませんが、浮動小数点数に整数を追加できます)。 。

型システムはVMによって使用され、命令のオペランドが有効であることを保証します。プログラマーは、関数に渡されたパラメーターが有効であること(つまり、正しい型であること)を保証するために使用できます。
型システムはサブタイプと複数の継承をサポートし(プログラマは両方の機能を利用できます)、オブジェクトのメソッドの動的ディスパッチを使用するときに型が考慮されます-VMは型を使用してチェックします特定のタイプに対して実装された特定のメッセージはどの関数か。

その後の質問は、「型は値にどのように割り当てられるのか?」でした。だから私はすべての値がボックス化されており、型の名前、それが応答するメッセージ、およびそれから継承する型に関する情報を提供する型定義構造を指すポインターを持っていることを説明しました。

その後、私は笑われ、「それは本当のタイプシステムではない」というコメントで私の答えは却下されました。

だから-私が説明したものが「実際のタイプシステム」として適格でない場合、どうなりますか?私が提供するものはタイプシステムと見なすことができないというのはその人の権利でしたか?

51
Mael

これはすべて、型システムが提供するものの詳細な説明のようです。そして、あなたの実装はそれがしていることに対して十分に合理的なもののように聞こえます。

言語によっては、ランタイムディスパッチを行わないため、ランタイム情報は必要ありません(または、vtablesまたは別のメカニズムを介して単一のディスパッチを行うため、型情報は必要ありません)。一部の言語では、名前や継承ではなく、型の等価性のみに関心があるため、シンボル/プレースホルダーを用意するだけで十分です。

あなたの環境によっては、その人はあなたの型システムにもっと形式主義を望んでいたかもしれません。彼らは、プログラマが何ができるかそれを使って(== --- ==)証明を知りたいdo それと。残念ながらこれは学界ではかなり一般的です。ただし、型システムに誤りがあり、正確性を逃れる可能性があるため、学者はそのようなことをします。これらの1つを発見した可能性があります。

さらに質問がある場合は、 タイプとプログラミング言語 がこの主題に関する標準的な本であり、学者が必要とする厳密さの一部と、物事を説明するのに役立つ用語のいくつかを学ぶのに役立ちます。

31
Telastyn

@Telastynの回答が特に好きです。なぜなら、形式主義へ​​の学術的関心への言及があるからです。

ディスカッションに追加させてください。

型システムとは何ですか?

型システムは、不正なプログラム状態を定義、検出、および防止するためのメカニズムです。制約を定義して適用することで機能します。制約の定義はtypesであり、制約の適用はtypesの使用法です。変数宣言内。

タイプ定義は通常、合成演算子をサポートします(たとえば、構造体、サブクラス化、列挙型、共用体などの分離型などのさまざまな形式の結合)。

制約、タイプの使用法は、時には合成演算子も許可します(たとえば、少なくともこれ、正確にこれ、これかそれとも、これは他の何かが成り立つことを条件とします)。

型システムがその言語で利用可能であり、コンパイル時にコンパイル時エラーを発行できるようにする目的で適用される場合、それは静的型システムです。これらは、多くの違法プログラムが実行されるだけでなくコンパイルすることを防ぎ、したがって、違法プログラムの状態を防ぎます。

(静的型システムは、プログラムが不平を言っている不健全なコードに到達することがわかっている(または決定できない)かどうかに関係なく、プログラムの実行を停止します。静的型システムは、特定の種類のナンセンス(宣言された制約違反)を検出します。実行する前にプログラムをエラーと判断します。)

型システムが実行時に適用される場合、それは動的な型システムであり、不正なプログラムの状態を防ぎます。

かなり一般的な型システムの提供は、静的機能と動的機能の両方を提供することです。

20
Erik Eidt

ああ、私はこの質問にできる限り答えようとすることに興奮しています。きちんと考えがきちんと整理できたらいいなと思います。

@Dovalが言及し、質問者が(失礼にではあるが)指摘したように、実際には型システムはありません。タグを使用した動的チェックのシステムがありますが、これは一般に非常に弱く、あまり面白くありません。

「型システムとは何か」という問題はかなり哲学的である可能性があり、その問題について異なる視点で本を書くことができます。ただし、これはプログラマー向けのサイトなので、できるだけ答えを実用的にするようにします(そして、型は極端にプログラミングで実用的です) 、一部の人が考えるかもしれないにもかかわらず)。

概観

より正式な基盤に入る前に、型システムが何に適しているかを理解することから始めましょう。 型システムはプログラムに構造を課します。これらは、さまざまな関数と式をどのように組み合わせるかを教えてくれます。構造がなければ、プログラムは実行不可能であり、非常に複雑であり、プログラマーのわずかな間違いでも危害を加える準備ができています。

タイプシステムでプログラムを書くことは、ブレーキをかけて、ドアを安全に閉める、エンジンに油をさすなど、未使用の状態で介護を運転するようなものです。タイプシステムなしでプログラムを書くことは、ヘルメットとホイールなしのモーターサイクルに乗るようなものです。スパゲッティのうち。あなたはあなたを完全に制御することはできません。

議論を根付かせるために、リテラル式_num[n]_および_str[s]_を備えた言語があり、それぞれ数値nおよび文字列sを表し、プリミティブ関数plusおよびconcat、意図した意味。明らかに、あなたは_plus "hello" "world"_や_concat 2 4_のようなものを書きたくありません。しかし、どうすればこれを防ぐことができますか? アプリオリ、数字の2を文字列リテラル「world」から区別する方法はありません。私たちが言いたいのは、これらの表現はさまざまなコンテキストで使用する必要があるということです。彼らは異なるタイプを持っています。

言語とタイプ

少し後戻りしましょう:プログラミング言語とは何ですか?一般に、プログラミング言語を2つの層に分割できます。構文とセマンティクスです。これらは、それぞれstaticsおよびdynamicsとも呼ばれます。型システムは、これら2つの部分間の相互作用を仲介するために必要であることがわかります。

構文

プログラムはツリーです。コンピューターで書くテキストの行にだまされてはいけません。これらは、人間が読めるプログラムの表現にすぎません。プログラム自体は抽象構文ツリーです。たとえば、Cでは次のように記述します。

_int square(int x) { 
    return x * x;
 }
_

これは、プログラム(フラグメント)のconcrete構文です。ツリー表現は次のとおりです。

_     function square
     /     |       \
   int   int x    return
                     |
                   times
                  /    \
                 x      x
_

プログラミング言語は、その言語の有効なツリーを定義する文法を提供します(具体的または抽象的な構文を使用できます)。これは通常、BNF表記法などを使用して行われます。作成した言語でこれを実行したと思います。

意味論

OK、プログラムが何かはわかっていますが、それは単なる静的なツリー構造です。おそらく、私たちはプログラムに実際に何かをcomputeさせたいと思います。セマンティクスが必要です。

プログラミング言語の意味論は、豊富な研究分野です。大まかに言って、2つのアプローチがあります:変分セマンティクスおよび操作セマンティクス。確率論的意味論は、プログラムをいくつかの基礎となる数学的構造(自然数、連続関数など)にマッピングすることによってプログラムを記述します。それは私たちのプログラムに意味を提供します。反対に、運用上のセマンティクスは、プログラムの実行方法を詳しく説明することによってプログラムを定義します。私の意見では、運用上のセマンティクスはプログラマー(自分自身を含む)にとってより直感的であるため、それを使いましょう。

正式な運用セマンティクスの定義方法については説明しません(詳細は少し複雑です)が、基本的には次のようなルールが必要です。

  1. _num[n]_は値です
  2. _str[s]_は値です
  3. _num[n1]_および_num[n2]_が整数に評価される場合_n_1$ and $n_2$, then_ plus(num [n1]、num [n2]) `は整数$ n_1 + n_2 $に評価されます。
  4. _str[s1]_および_str[s2]_が文字列s1およびs2に評価される場合、concat(str[s1], str[s2])は文字列s1s2に評価されます。

などルールは実際にはかなり正式ですが、要点はわかります。しかし、すぐに問題が発生します。次のように書くとどうなりますか:

_concat(num[5], str[hello])
_

うーん。これはかなり難問です。数値と文字列を連結する方法についてのルールはどこにも定義していません。そのようなルールを作成することもできますが、この操作は無意味であることを直感的に理解しています。このプログラムを有効にしたくない。したがって、容赦なくタイプに導かれます。

タイプ

プログラムは、言語の文法で定義されたツリーです。プログラムは、実行ルールによって意味が与えられます。ただし、一部のプログラムは実行できません。つまり、一部のプログラムは無意味です。これらのプログラムは型が間違っています。したがって、タイピングは、言語で意味のあるプログラムを特徴付けます。プログラムが適切に型付けされている場合、それを実行できます。

いくつか例を挙げましょう。繰り返しになりますが、評価規則と同様に、タイピング規則も非公式に提示しますが、厳密にすることもできます。ここにいくつかのルールがあります:

  1. _num[n]_形式のトークンのタイプはnatです。
  2. _str[s]_形式のトークンのタイプはstrです。
  3. 式_e1_の型がnatで、式_e2_の型がnatの場合、式plus(e1, e2)の型はnatになります。
  4. 式_e1_の型がstrで、式_e2_の型がstrの場合、式concat(e1, e2)の型はstrになります。

したがって、これらのルールによれば、plus(num[5], num[2])には型natがありますが、plus(num[5], str["hello"])に型を割り当てることはできません。プログラム(または式)は、任意の型を割り当てることができる場合は型が正しく、そうでない場合は型が正しくないと言えます。型システムはsoundすべての適切に型付けされたプログラムを実行できる場合です。 Haskellは音です。 Cはそうではありません。

結論

タイプには他の見方があります。ある意味での型は直観主義的な論理に対応しており、カテゴリー理論ではオブジェクトと見なすこともできます。これらの接続を理解することは魅力的ですが、プログラミング言語を作成したり、設計したりするだけの場合は必須ではありません。ただし、プログラムの形成を制御するためのツールとして型を理解することは、プログラミング言語の設計と開発に不可欠です。私は、タイプが表現できるもののほんの表面をひっかいただけです。あなたの言語に組み込むのに十分な価値があるとあなたが思っていることを願っています。

14
gardenhead