ポール・グラハムのいくつかのポイントを理解するのに助けが必要ですLisp が他と違う点。
変数の新しい概念。Lisp では、すべての変数は実質的にポインタです。型を持つのは値であり、変数ではありません。変数を割り当てたりバインドしたりするということは、ポインタが指すものではなく、ポインタをコピーすることを意味します。
シンボル型。シンボルは、ポインタを比較することで等価性をテストできるという点で文字列とは異なります。
シンボルのツリーを使用したコードの表記法。
言語全体が常に利用可能です。読み取り時、コンパイル時、実行時に実質的な区別はありません。読み取り中にコードをコンパイルまたは実行したり、コンパイル中にコードを読み取りまたは実行したり、実行時にコードを読み取りまたはコンパイルしたりできます。
これらのポイントは何を意味しますか? C や Java などの言語ではどのように異なりますか? Lisp ファミリー言語以外の言語には現在、これらの構成要素がありますか?
ベストアンサー1
Matt の説明はまったく問題ありません。彼は C と Java との比較を試みていますが、私はそうしません。しかし、どういうわけか、私はこのトピックについて時々議論するのが本当に好きなので、ここで私の回答を試してみます。
(3)と(4)について:
あなたのリストのポイント(3)と(4)は、最も興味深く、現在でも関連性があるようです。
これらを理解するには、Lisp コードが実行される途中で、プログラマーが入力した文字のストリームの形で何が起こるかを明確に把握しておくと便利です。具体的な例を見てみましょう。
;; a library import for completeness,
;; we won't concern ourselves with it
(require '[clojure.contrib.string :as str])
;; this is the interesting bit:
(println (str/replace-re #"\d+" "FOO" "a123b4c56"))
このスニペットのクロージュアコードは を出力しますaFOObFOOcFOO
。読み取り時間がユーザー コードに対して実際にはオープンではないため、Clojure はリストの 4 番目のポイントを完全に満たしていない可能性があることに注意してください。ただし、それ以外の場合の意味については後で説明します。
では、このコードをどこかのファイルに入れて、Clojure に実行させるとします。また、(簡単にするために) ライブラリのインポートを終えたと仮定します。興味深い部分は、一番右から始まり(println
、一番右で終わります)
。これは予想どおりに字句解析/解析されますが、すでに重要な点が浮かび上がっています。結果は特別なコンパイラ固有のAST表現ではなく、通常のClojure / Lispデータ構造です。つまり、一連のシンボル、文字列、そしてこの場合はリテラルに対応する単一のコンパイル済み正規表現パターン オブジェクトを含むネストされたリスト#"\d+"
です (詳細は後述)。一部の Lisp はこのプロセスに独自の工夫を加えていますが、Paul Graham は主に Common Lisp について言及していました。ご質問に関連する点では、Clojure は CL に似ています。
コンパイル時の言語全体:
この時点以降、コンパイラが扱うのは (これは Lisp インタープリタにも当てはまります。Clojure コードは常にコンパイルされます)、Lisp プログラマが操作に慣れている Lisp データ構造だけです。この時点で、素晴らしい可能性が明らかになります。Lisp プログラマが、Lisp プログラムを表す Lisp データを操作し、変換されたプログラムを表す変換されたデータを出力する Lisp 関数を記述して、元のプログラムの代わりに使用できるようにしたらどうでしょうか。言い換えると、Lisp プログラマが自分の関数を、Lisp ではマクロと呼ばれる一種のコンパイラ プラグインとして登録できるようにしたらどうでしょうか。実際、まともな Lisp システムならどれでもこの機能を備えています。
したがって、マクロは、実際のオブジェクト コードが出力される最終コンパイル フェーズの前のコンパイル時に、プログラムの表現に対して動作する通常の Lisp 関数です。マクロが実行できるコードの種類に制限がないため (特に、マクロが実行するコード自体がマクロ機能を多用して記述されていることが多い)、「言語全体がコンパイル時に使用可能」であると言えます。
読み取り時の言語全体:
正規表現リテラルに戻りましょう#"\d+"
。上で述べたように、これは、コンパイラがコンパイル用に準備されている新しいコードの最初の言及を聞く前に、読み取り時に実際のコンパイル済みパターン オブジェクトに変換されます。これはどのように起こるのでしょうか。
まあ、Clojureの現在の実装方法は、ポール・グラハムが考えていたものとは多少異なりますが、巧妙なハックCommon Lisp では、概念的にはもう少しわかりやすくなります。ただし、基本は同じです。Lisp Reader は状態遷移を実行し、最終的に「受け入れ状態」に達したかどうかを宣言するだけでなく、文字が表す Lisp データ構造を吐き出す状態マシンです。したがって、文字は123
数値123
などになります。ここで重要な点があります。このステートマシンはユーザーコードによって変更できる(前述のように、これは CL の場合にはまったく当てはまりますが、Clojure の場合はハック(非推奨であり、実際には使用されていません)が必要です。しかし、話がそれてしまいましたが、私が詳しく説明するのは PG の記事なので...)
したがって、もしあなたがCommon Lispプログラマーで、Clojureスタイルのベクトルリテラルのアイデアが気に入ったら、文字シーケンスに適切に反応する関数をリーダーにプラグインするだけで済みます。[
あるいは#[
、それを対応する で終わるベクトルリテラルの開始として扱うこともできます]
。このような関数は、リーダーマクロ通常のマクロと同様に、事前に登録されたリーダー マクロによって有効にされたファンキーな表記法で記述されたコードを含む、あらゆる種類の Lisp コードを実行できます。つまり、読み取り時に言語全体が利用可能になります。
まとめ:
実際、ここまでに実証されたのは、通常の Lisp 関数を読み取り時またはコンパイル時に実行できることです。読み取り時、コンパイル時、または実行時に読み取りとコンパイル自体がどのように可能になるかを理解するために必要な 1 つのステップは、読み取りとコンパイル自体が Lisp 関数によって実行されることを認識することです。いつでもread
または を呼び出すeval
だけで、それぞれ文字ストリームから Lisp データを読み込んだり、Lisp コードをコンパイルして実行したりできます。これが常に言語全体です。
Lisp がリストのポイント (3) を満たしているという事実が、ポイント (4) を満たす方法にとって不可欠であることに注目してください。Lisp によって提供されるマクロの特定のフレーバーは、コードが通常の Lisp データで表現されることに大きく依存しており、これは (3) によって可能になります。ちなみに、ここではコードの「ツリー風」の側面だけが本当に重要です。XML を使用して Lisp を記述することも考えられます。