7

Lisp コードのファイルをバイトコードまたは生のアセンブリ (または fasl ファイル) にコンパイルするときに、読み取りマクロがどうなるかを理解するのに少し苦労しています。あるいは、わかっているけれどわからないということもあるでしょう。私は本当に混乱しています。

読み取りマクロを使用する場合、ソースを用意する必要はありませんか?

その場合、読み取りマクロの関数を構成するソース コードを実行する必要があります。そうしないと、次のようなことができるときにどのように機能しますread-charか?

そのために、読み取りマクロに事前に定義された変数を使用させたい場合は、その前にすべてのコードを実行する必要があるため、これがランタイムになり、すべてが台無しになります。

その前にコードを実行しないと、上で定義されたものは利用できません。

読み取りマクロを定義する関数またはコンパイラ マクロはどうでしょうか。requireあなたやloadファイル、またはコンパイルされていないものでない限り、それらはまったく機能しないと思います。しかし、それらがコンパイルされた場合、それらは使用できなくなりますか?

私の推測の一部が正しければ、ファイル全体をコンパイルして後で実行するか、または後で実行するかによって、「マクロで使用できるデータ」と「関数で使用できるマクロ」に大きな違いがあることを意味します。一度に 1 行ずつファイルを解釈します (つまり、式を次々と読み取り、コンパイルし、評価します)。

要するに、1行をそれ以上のマクロ処理などをしなくても実行できる形にコンパイルするには、前の行を読み込んでコンパイルし、実行する必要があるようです。

これらの質問は、各行が入ってくるたびに実行できる場所を解釈するのではなく、lisp のコンパイルに適用されることをもう一度思い出してください。

とりとめのないことで申し訳ありませんが、私はLispが初めてで、それがどのように機能するかをもっと知りたいです.

4

3 に答える 3

5

これは実際には興味深い質問であり、Lisp プログラマーの多くが苦労しています。これの主な理由の 1 つは、ほとんどすべてが「期待どおり」に機能し、Lisp のより高度な機能を使い始めて初めて、これらのことについて本当に考え始めることです。

あなたの質問に対する簡単な答えは、はい、コードが適切にコンパイルされるためには、以前のコードの一部が実行されている必要があるということです。some という言葉に注意してください。それが鍵です。小さな例を作ってみましょう。次の内容のファイルを考えてみましょう。

(print 'a)

(defmacro bar (x) `(print ,x))

(bar 'b)

すでにわかっているようCOMPILE-FILEに、このファイルを実行すると、結果の.faslファイルには次のコードのコンパイル済みバージョンが含まれます。

(print 'a)
(print 'b)

「しかし」、「DEFMACROコンパイル中にフォームが実行されたのに、フォームが実行されPRINTなかったのはなぜですか?」と尋ねるかもしれません。答えは Hyperspec セクション3.2.3で説明されています。次の文が含まれています。

通常、compile-file でコンパイルされたファイルに表示される最上位のフォームは、ファイルがコンパイルされたときではなく、コンパイルされた結果のファイルがロードされたときにのみ評価されます。ただし、通常、ファイル内の一部のフォームをコンパイル時に評価して、ファイルの残りの部分を正しく読み取ってコンパイルできるようにする必要があります。

フォームがいつ評価されるかを正確に制御するために使用できるフォームがあります。EVAL-WHENこの目的で使用します。実際、これはまさに Lisp コンパイラがDEFMACROそれ自体を実装する方法です。REPL から次のように入力すると、Lisp がそれをどのように実装しているかを確認できます。

(macroexpand '(defmacro bar (x) `(print ,x)))

明らかに、異なる Lisp 実装ではこれを異なる方法で実装しますが、重要な点は、定義を次の形式でラップすることです(eval-when (:compile-toplevel :load-toplevel :execute) ...)。これは、ファイルがコンパイルされるときだけでなく、ファイルがロードされるときにもフォームを評価する必要があることをコンパイラに伝えます。これを行わないと、定義されたのと同じファイルでマクロを使用できなくなります。ファイルがコンパイルされたときにのみフォームが評価された場合、マクロを読み込んだ後、別のファイルでマクロを使用することはできません。

于 2012-07-19T05:38:04.747 に答える
4

ファイルのコンパイルはCommonLispで定義されています:CLHSセクション3.2.3ファイルのコンパイル

コンパイル中:読み取りマクロを使用してフォームを使用するには、その読み取りマクロの実装をコンパイラーが使用できるようにする必要があります。

通常、このような依存関係はdefsystem、システムのさまざまなファイル(プロジェクトなど)間の依存関係が記述されている機能で処理されます。特定のファイルをコンパイルするには、別のファイル(できればコンパイルされたバージョン)をコンパイル中のLispにロードする必要があります。

ここで、読み取りマクロを定義し、同じファイルにその表記を使用するフォームを作成する場合は、コンパイラが読み取りマクロとその実装について認識していることを再度確認する必要があります。ファイルコンパイラにはコンパイル環境があります。デフォルトでは、同じファイルのコンパイル済み関数をこの環境にロードしません。

コンパイラにファイル内の特定のコードを認識させるために、CommonLispが提供するコンパイルを行いますEVAL-WHEN

読み取りマクロの例を見てみましょう。

(set-syntax-from-char #\] #\)) 

(defun reader-example (stream char)
  (declare (ignore char))
  (let ((class (read stream t nil t))
        (args (read-delimited-list #\] stream t)))
    (apply #'make-instance
           class
           args)))

(set-macro-character #\[ 'reader-example)

(defclass example ()
  ((name :initarg :name)))

(defvar *examples*
  (list [example :name e1]
        [example :name e2]
        [example :name e3]))

上記のソースをロードすると、すべて問題ありません。ただし、ファイルコンパイラを使用する場合、最初にロードしないとコンパイルされません。たとえば、ファイルコンパイラはCOMPILE-FILE、パス名を使用して関数を呼び出すことによって呼び出されます。

次に、ファイルをコンパイルします。

(set-syntax-from-char #\] #\)) 

上記はコンパイル時に実行されません。新しい構文の変更は、コンパイル時には利用できません。

(defun reader-example (stream char)
  (declare (ignore char))
  (let ((class (read stream t nil t))
        (args (read-delimited-list #\] stream t)))
    (apply #'make-instance
           class
           args)))

上記の関数はコンパイルされますが、ロードされません。この実装は、後のステップでコンパイラーが使用することはできません。

(set-macro-character #\[ 'reader-example)

繰り返しますが、上記のフォームは実行されません。そのためのコードだけが生成されます。

(defclass example ()
  ((name :initarg :name)))

コンパイラはクラスを記録しますが、後でそのインスタンスを作成することはできません。

(defvar *examples*
  (list [example :name e1]
        [example :name e2]
        [example :name e3]))

上記のコードはエラーをトリガーします。これは、読み取りマクロがコンパイル時に使用できないためです。これは、以前にロードされたことがない場合を除きます。

現在、2つの簡単な解決策があります。

  • 読み取りマクロの実装を別のファイルに入れ、読み取りマクロを使用するファイルの前にコンパイルおよびロードされていることを確認します。

  • EVAL-WHENコンパイル時に有効にする必要のあるコードを回避します。

例:

(EVAL-WHEN (:compile-toplevel :load-toplevel :execute)
  (do-something-also-at-compile-time))

上記はコンパイラーによって認識され、実行されます。ここで、コンパイル時にコードが呼び出すすべてのもの(必要なすべての定義)が含まれていることを確認する必要があります。

言うまでもなく、このようなコンパイルの依存関係を可能な限り減らすのは良いスタイルです。通常、必要な機能を別のファイルに入れ、それを使用するファイルをコンパイルする前に、このファイルがコンパイルされ、コンパイル中のLispにロードされることを確認してください。

于 2012-07-19T05:48:28.257 に答える
1

マクロ (読み取りマクロを含む) は単なる関数であり、他のすべての関数と同じように扱われます。関数またはマクロがコンパイルされた後は、ソース コードを保持する必要はありません。

多くの Lisp 実装は、まったく解釈を行いません。たとえば、デフォルトでは SBCL はコンパイルのみを行い、 の場合でも解釈モードに切り替えませんeval。重要なニュアンスは、Common Lisp のコンパイルがインクリメンタルであることです (分離コンパイルとは対照的に、C や Java などの多くの Scheme 実装や言語で一般的です)。これにより、関数やマクロをコンパイルして、同じ "コンパイルユニット」。

于 2012-07-19T05:31:57.643 に答える