13

さて、最初にこれを邪魔にならないようにします:私は次の答えを読みました:

Lisp はどのように動的でコンパイルされていますか?

しかし、私はその答えを本当に理解していません。

Python のような言語では、式:

x = a + b

実際にはコンパイルできません。「コンパイラ」に関しては、a と b の型を知ることができず (型は実行時にしかわからないため)、したがってそれらを追加する方法もわかりません。

これが、Python のような言語を型宣言なしではコンパイルできない理由ですよね? 宣言を使用すると、コンパイラは、たとえば a と b が整数であることを認識しているため、それらを追加する方法とそれをネイティブ コードに変換する方法を認識しています。

では、次のようにします。

(setq x 60)
(setq y 40)
(+ x y)

仕事?

コンパイル済みは、ネイティブの事前コンパイルとして定義されています。

編集

実際には、この質問は、型宣言のない動的言語をコンパイルできるかどうかに関するものです。

編集2

多くの調査 (つまり、熱心なウィキペディアの閲覧) の結果、次のことを理解していると思います。

  • 動的型付き言語は、実行時に型がチェックされる言語です
  • 静的型付き言語は、プログラムのコンパイル時に型がチェックされる言語です
  • 型宣言により、コンパイラは常に API 呼び出しを行う代わりに、よりネイティブな「関数」を使用できるため、コードをより効率的にすることができます (そのため、Cython コードに型宣言を追加して高速化することができますが、 C コードで Python ライブラリを呼び出すことができるため)
  • Lisp にはデータ型はありません。したがって、チェックする型はありません (型はデータそのものです)
  • Obj-C には静的宣言と動的宣言の両方があります。前者はコンパイル時に型チェックされ、後者は実行時に型チェックされます

上記の点で間違っている場合は修正してください。

4

2 に答える 2

7

コンパイルは、ある言語から別の言語への単純な翻訳です。A同じものを言語と言語で表現できれば、言語で表現されたものを言語で同じものにBコンパイルできます。AB

ある言語で意図を表現したら、それは解釈されることによって実行されます。C やその他のコンパイル済み言語を実行している場合でも、ステートメントは次のようになります。

  1. C からの翻訳 -> アセンブリ言語
  2. アセンブリから翻訳 -> マシンコード
  3. 機械によって解釈されます。

コンピューターは、実際には非常に基本的な言語のインタープリターです。それは非常に基本的で扱いが難しいため、より扱いやすく、機械語 (C など) の同等のステートメントに簡単に変換できる他の言語が考え出されました。次に、JIT コンパイラーのように「オンザフライ」で変換を実行するか、高級言語 (LISP や Python など) でステートメントを直接実行する独自のインタープリターを作成することで、コンパイル フェーズを乗っ取ることができます。

ただし、インタープリターはコードを直接実行するためのショートカットにすぎないことに注意してください。コードを実行する代わりに、インタープリターが実行する呼び出しを出力した場合、コードを実行するには... コンパイラーが必要です。もちろん、それは非常にばかげたコンパイラであり、持っている情報のほとんどを利用できません。

実際のコンパイラは、コードを生成する前に、プログラム全体からできるだけ多くの情報を収集しようとします。たとえば、次のコード:

const bool dowork = false;

int main() {
    if (dowork) {
        //... lots of code go there ... 
    }
    return 0;
}

if理論的には、ブランチ内のすべてのコードを生成します。しかし、賢いコンパイラは、プログラム内のすべてを知っていて、それdoworkが常にfalse.

それに加えて、一部の言語にはがあり、関数呼び出しをディスパッチするのに役立ち、コンパイル時にいくつかのことを保証し、機械語への変換に役立ちます。C などの一部の言語では、プログラマーが変数の型を宣言する必要があります。LISP や Python などの他のものは、変数が設定されたときに変数の型を推測するだけで、別の型が必要な場合に特定の型の値を使用しようとすると、実行時にパニックになります (たとえば(car 2)、ほとんどの Lisp インタープリターで記述した場合、エラーが発生します)。ペアが必要であることを示すエラー)。型を使用してコンパイル時にメモリを割り当てることができます (たとえば、C コンパイラは、10 * sizeof(int)を割り当てる必要がある場合、正確にバイトのメモリを割り当てますint[10])。ただし、これは正確には必要ありません。. 実際、ほとんどの C プログラムはポインタを使用して配列を格納しますが、これは基本的に動的です。ポインターを処理するとき、コンパイラーは実行時に必要なチェックや再割り当てなどを実行するコードを生成/リンクします。Python または Lisp インタープリターはコンパイルされたプログラムですが、それでも動的な値に作用できます。実際、アセンブリ言語自体は実際には型付けされていません。コンピューターが「認識する」のはビットのストリームとビットに対する操作だけであるため、コンピューターは任意のオブジェクトに対して任意の操作を実行できます。高水準言語は、任意の型と制限を導入して、物事を読みやすくし、完全にクレイジーなことをしないようにします。しかし、これはあなたを助けるためのものであり、絶対的な要件ではありません.

哲学的な暴言が終わったので、あなたの例を見てみましょう:

(setq x 60)
(setq y 40)
(+ x y)

それを有効な C プログラムにコンパイルしてみましょう。それが完了すると、C コンパイラが豊富にあるので、LISP -> C -> 機械語、またはその他のほとんどすべてを翻訳できます。コンパイルは翻訳にすぎないことに注意してください (最適化もクールですが、オプションです)。

(setq 

これは値を割り当てます。しかし、何が何に割り当てられているかはわかりません。続けましょう

(setq x 60)

わかりました、x に 60 を割り当てます。60 は整数リテラルなので、その C 型はint. が別の型であると仮定する理由がないためx、これは C:

int x = 60;

同様に(setq y 40):

int y = 40;

これで、次のようになりました。

(+ x y)

+実装に応じて、いくつかのタイプの引数を取ることができる関数ですが、xyが整数であることはわかっています。私たちのコンパイラは、次の同等の C ステートメントが存在することを認識しています。

x + y;

だから私たちはそれを翻訳するだけです。最終的な C プログラム:

int x = 60;
int y = 40;
x + y;

これは完全に有効な C プログラムです。これよりもさらにトリッキーになる可能性があります。たとえば、xyが非常に大きい場合、ほとんどの LISP はそれらをオーバーフローさせませんが、C はオーバーフローさせません。そのため、コンパイラをコーディングして、int の配列 (または関連するもの) として独自の整数型を持たせることができます。これらの型に対して一般的な操作 ( など) を定義できる場合+、新しいコンパイラは以前のコードを代わりに次のように変換する可能性があります。

int* x = newbigint("60");
int* y = newbigint("40");
addbigints(x, y);

関数newbigintを使用addbigintsして、他の場所で定義するか、コンパイラによって生成されます。まだ有効な C であるため、コンパイルされます。実際、独自のインタープリターはおそらく低レベル言語で実装されており、独自の実装で LISP オブジェクトの表現を既に持っているため、これらを直接使用できます。

ちなみに、それはまさにCythonコンパイラがPythonコードに対して行うことです:)

Cython で型を静的に定義して、速度と最適化を向上させることができますが、必須ではありません。Cython は、Python コードを直接 C に変換し、次にマシン コードに変換できます。

より明確になることを願っています!覚えて:

  1. すべてのコードが解釈され、最終的に
  2. コンパイラは、コードを解釈しやすい/速いものに変換します。多くの場合、途中で最適化を実行しますが、これは定義の一部ではありません
于 2013-08-21T13:09:11.753 に答える