1. ホーム
  2. clojure

[解決済み] Lispに関するPaul Grahamの指摘を説明してください [closed].

2022-05-10 13:36:11

質問

ポール・グラハムの 何がLispを違ったものにしたのか .

  1. 変数の新しい概念。Lispでは、すべての変数は事実上ポインタである。変数ではなく値が型を持っており、変数の代入や束縛はポインタをコピーすることを意味し、それが何を指しているかはわかりません。

  2. シンボル型です。シンボルは文字列と異なり、ポインタを比較することで等価性をテストすることができます。

  3. シンボルのツリーを使用したコードの表記法。

  4. 言語全体が常に利用可能。読み出し時、コンパイル時、実行時という区別は実際にはありません。読みながらコードをコンパイルしたり実行したり、コンパイルしながらコードを読んだり実行したり、実行時にコードを読んだりコンパイルしたりすることができる。

これらの点はどのような意味があるのでしょうか?CやJavaのような言語ではどう違うのでしょうか?Lisp系の言語以外で、これらの構成要素を持つ言語は現在あるのでしょうか?

どのように解決するのですか?

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"))

このスニペットの Clojure のコードが出力されます。 aFOObFOOcFOO . Clojureは、リードタイムがユーザーコードに対して本当にオープンではないので、間違いなく、リストの4番目のポイントを完全に満たしていないことに注意してください; しかし、私はこれがそうであるために何を意味するのかを議論します。

では、このコードがどこかのファイルにあり、Clojureにそれを実行するように頼んだとします。また、(簡単のために)ライブラリのインポートを通過したと仮定しましょう。面白いのは (println で始まり ) で終わっています。これは期待通りにレキシング/パースされるのですが、すでに重要なポイントが発生しています。 結果はコンパイラ固有のAST表現ではなく、通常のClojure / Lispデータ構造です。 すなわち、シンボル、文字列、そしてこの場合は #"\d+" リテラルを含むネストしたリストです(詳細は後述します)。Lispの中にはこのプロセスに独自の工夫を凝らしたものもありますが、Paul Grahamは主にCommon Lispについて述べています。あなたの質問に関連する点で、ClojureはCLに似ています。

コンパイル時に言語全体が

この時点で、コンパイラが扱うのは、Lispプログラマが操作に慣れているLispデータ構造だけです(これはLispインタプリタにも当てはまります;Clojureコードはたまたま常にコンパイルされています)。このとき、Lispプログラマは、Lispプログラムを表すLispデータを操作し、変換されたプログラムを表す変換データを出力するLisp関数を書いて、元のプログラムの代わりに使用することができる、という素晴らしい可能性が見えてきます。言い換えれば、Lispプログラマは自分の関数をコンパイラのプラグイン(Lispではマクロと呼ばれる)として登録できるようにすればよいのです。まともなLispシステムであれば、このような機能を備えています。

つまり、マクロはコンパイル時にプログラムの表現に対して動作する通常の Lisp 関数であり、実際のオブジェクトコードが生成される最終的なコンパイル段階の前に使用されます。マクロが実行できるコードの種類に制限はないため (特に、マクロが実行するコード自体が、マクロ機能を自由に使って書かれていることが多い)、「コンパイル時に言語全体が利用可能である」と言うことができるのです。

読み込み時に言語全体が利用可能。

戻ってみましょう #"\d+" 正規表現リテラルに戻りましょう。前述のように、これは読み込み時に実際のコンパイル済みパターン オブジェクトに変換され、コンパイラがコンパイル準備中の新しいコードの最初の言及を聞く前に変換されます。これはどのように行われるのでしょうか?

Clojureが現在実装されている方法は、Paul Grahamが考えていたこととは多少異なります。 を巧みにハックして . Common Lispでは、概念的にはもう少しすっきりしているはずです。Lisp Readerは状態遷移を行い、最終的にquot;accepting state"に達したかどうかを宣言するのに加えて、文字が表すLispデータ構造を吐き出すステートマシンです。したがって、文字 123 は数字 123 などとなる。重要なポイントはこれからです。 このステートマシンはユーザーコードによって変更することができます . (先に述べたように、CLの場合は完全に正しい。Clojureの場合はハック(discouraged & 実際には使われていない)が必要である。しかし、私は脱線し、それは私が詳しく説明することになっているPGの記事です、それで...)

つまり、もしあなたがCommon Lispプログラマで、たまたまClojureスタイルのベクトルリテラルのアイデアが好きなら、ある文字シーケンスに適切に反応する関数をリーダーにプラグインすればよいのです --。 [ または #[ で終わるベクターリテラルの始まりとして扱われます。 ] . このような関数は リーダーマクロ と呼ばれ、通常のマクロと同様に、Lispのあらゆる種類のコードを実行することができ、それ自身、以前に登録されたリーダマクロによって可能になったファンキーな記法で書かれたコードも含みます。つまり、読み出し時に言語全体を使用することができるのです。

まとめ。

実は、ここまでの説明では、通常のLisp関数を読み込み時やコンパイル時に実行できることが示されています。ここから、読み込み時やコンパイル時、実行時にどのように読み込みやコンパイルが可能かを理解するには、読み込みやコンパイルがLisp関数によって行われていることを理解することが必要な一歩になります。あなたはただ read または eval で、それぞれ文字列からLispデータを読み込んだり、Lispコードをコンパイル&ア ンプ、実行したりすることができます。これは言語全体が常にそこにあることを意味します。

Lispが(3)を満たしていることは、(4)を満たすために重要です。Lispが提供するマクロの特殊性は、コードが通常のLispデータで表現されることに大きく依存しており、(3)によって可能になるものなのです。ちなみに、ここで重要なのはコードのツリー的な側面だけで、XMLで書かれたLispもあり得ます。