1

これが私がやろうとしていることです:

(defun test-macrolet ()
  (macrolet
      ((%test 
        (x)
        (message "%%test is called")
        `(message "x: %s" ,x))
       (%aref (array place)
              `(aref ,array ,place)))
    ;; it doesn't expand to (message "x: %s" 100)
    (message "expanded: %s" (macroexpand-all '(%test 100)))
    (%test 100)
    (%aref (%test 100) 0)))

この関数を別のマクロの内部から呼び出しています。ローカルで定義する必要のあるすべての内部マクロを展開したいと思います。おそらく、あなたにもっと良いアイデアを与えるために、私はこれが欲しいです:

(iter (for i from 0 to 10)
      (when (oddp i) (collect i)))

forマクロとが定義されている環境で拡張しますがcollect、これらのマクロをグローバルに定義したくありません(明らかに、グローバル名前空間に入れるのに最適な名前ではありません)。

macroletローカルマクロを定義することで、ローカルで定義されたマクロを含むフォームを展開してから展開を返すことができますが、ローカルで定義されたマクロを認識しない(そして展開しない)ように動作macroexpandすることを望んでいました。macroexpand-all

さて、私が自分自身を明確にしていることを願っています...

編集

回答者が削除することを決めた回答のおかげで、私はEmacsLispで字句環境を取得する方法を探し始めました。私はこのサンプル関数を見つけました:

(defun byte-compile-make-lambda-lexenv (form)
  "Return a new lexical environment for a lambda expression FORM."
  ;; See if this is a closure or not
  (let ((args (byte-compile-arglist-vars (cadr form))))
    (let ((lexenv nil))
      ;; Fill in the initial stack contents
      (let ((stackpos 0))
    ;; Add entries for each argument
    (dolist (arg args)
      (push (cons arg stackpos) lexenv)
      (setq stackpos (1+ stackpos)))
    ;; Return the new lexical environment
    lexenv))))

bytecomp.elで。表面的には、環境が関数(私の場合はマクロ)の名前とその「スタック上の位置」で構成された単なるリストであることを意味します。2番目の部分はよくわかりませんが、実験してみます。見る...

編集2

OK、これはうまくいくようです:

(defun test-macrolet ()
  (macrolet
      ((%test 
        (x)
        (message "%%test is called")
        `(message "x: %s" ,x))
       (%aref (array place)
              `(aref ,array ,place)))
    (%test 100)
    (%aref (%test 100) 0)
    (message "expanded: %S" 
             (macroexpand
              '(%test 100)
              (cons `(,@(list (intern "%test")) . 
                      (lambda (y)
                        (message "Called from environment '%s'" y)
                        `(message "Final expansion '%s'" ,y)))
                    macroexpand-all-environment)))))

(test-macrolet)
"expanded: (message \"Final expansion '%s'\" 100)"

ずさんなコードでごめんなさい。さらに詳しい情報:macroexpand-all-environmentコンパイラ/インタプリタが現在の環境を格納する変数です。環境オブジェクトには(expander-name . expander-function)ペアが含まれています。ペアの車はマクロ展開のこのステップで検出されたシンボルであり、cdrは展開を処理する必要がある関数です。

編集3

macroletこれは読者を非常に混乱させるように思われるので、コードを生成する前に、を含むフォームを返すのではなく、すべてを展開したい理由はここにあります。

トップレベルのフォームを処理している間、私はたくさんのオブジェクトを作成しています。それをAST(抽象構文木)と呼びますが、実際にはそうではなく、これから行うコードの複雑なモデルにすぎません。マクロから生成します。マクロが繰り返し展開されると、その後の展開でASTが失われます。さらに、次の展開ステップを適切に生成できず、コードに対して特定の操作を実行できなくなります。たとえば、一部のフォームは依存しています。それらの漂遊論理部分は、「外国の」表現のより深いところに存在します。多くの場合、コードを生成してからマクロ展開メカニズムに依存する方が本当に簡単です。したがって、たとえば、@6502に答える:

forフォームが本体の前で閉じられているとすると、(for ...)は何に拡張する必要がありますか?

通常の意味での拡張ではありません。フォームが解析され、その一部が特別なオブジェクトに抽出され、後で最終的なコードの生成に使用されます。例えば:

(iter (for var in '(1 2 3 4))
      (when (oddp var) (collect (next var)))

最初に、2つの変数を登録するオブジェクトとvar、結果が格納される自動生成された変数を作成します。それを呼び出しましょう--0(これが、実際に名前を生成する方法だからです)。--0内の2番目の式を解析した後でのみ、変数が必要であることがわかりますiter。したがって、for非常に早い段階letで式に展開し、その最初の展開の後で初めて、変数をもう1つ追加する必要があることに気づきました。これでは遅すぎます。つまり、上記の展開は次のようになります。

(let (--0 var (--1 '(1 2 3 4)))
  (while --1
     (setq var (car --1) --1 (cdr --1))
     (if (oddp var) (setq --0 (cons (cadr --l) --0))))
  --0)

前の式を半分拡張してを生成する必要がある場合macrolet、どの変数に収集する必要があるか、next式を生成する方法などがわかりません。

また、collectが単なるローカル関数ではなくマクロである必要があるのはなぜですか?

どちらの要件もありません。適切なコードを生成できるように、マクロ展開プロセス中に通知する必要があります。技術的には、何にも拡張されません(たとえば、コード分析によって到達不能コードが作成されることが示された場合、拡張中に無視される可能性もあります)。

編集4

これは本当に長くなっています、ごめんなさい。拡張プロセス全体で保持しなければならないスキーマと拡張の状態に加えて、別の理由があります。次の2つの例を考えてみましょう。

(iter (for (key . value) in some-hash-table)
      (message "key: %s => value: %s" key value))

このコードはループを生成するの(while ...)は非効率的であるため、ループを生成することは期待されていません(maphash ...)。ここで使用することをお勧めします。でも:

(iter (for (key . value) in some-hash-table)
      (for (key-2 . value-2) in some-other-hash-table)
      (message "key: %s => value: %s" (list key key-2) (list value value-2)))

(maphash ...)2番目のハッシュテーブル(または最初のハッシュテーブルのいずれか小さい方)のキーを収集するための追加のフォームを生成する必要があり、以前に生成された可能性のある最初の式の前のセクションにかなりのコードを入力する必要があります。したがって、ここでマクロレットを生成した場合、すでに生成されているコードを生成する必要がある状況で立ち往生します。つまり、式が古くなっているため、戻って再解析する必要があります(私は幸運で、どういうわけかまだそこに到達することができます)。

4

2 に答える 2

1

あなたが何をしようとしているのかまだわかりません。ただし、作成したコードはmacroexpand-allマクロ展開時ではなく実行時に実行されるため、その時点では%textが存在しません((%test 100)マクロ展開が適切に行われるはずの2番目のコードとは異なります)。

私はあなたが望むものは次の線に沿っていると思います:

(defmacro iter (&rest elems)
  `(macrolet ((for (..) ...)
              (collect (..) ...))
     ,@elems))

あなたが言うようにforcollectマクロのさまざまな展開中にローカル状態を維持する必要がある場合は、macrolet完全に削除して、次のようなものを使用する必要があります。

(defmacro iter (&rest elems)
  (let (..some.local.state..)
    (let ((body (macroexpand-all `(progn elems)
                                 `((for  . ,(lambda (..) ...))
                                   (loop . ,(lambda (..) ...))
                                   ,@macroexpand-all-environment))))
     ...)))

macroletこれは、内部で行われていることを再現するだけです。

于 2012-11-23T18:32:12.697 に答える
0

あなたがすべきことは、を含むマクロコードから戻ることですmacrolet。マクロから返されるものはすでにマクロ展開されているため、明示的なマクロ展開を自分で行う必要はほとんどありません。

macroletコンパイラがマクロの非表示を適切に処理するためにsymbol-macroletウォーキングを行うため、ほとんどの場合、コードのウォーキングから解放されます。

マクロ展開の必要性を明示的に見つけた唯一のケースは、別のローカルマクロが使用されているかどうかに応じて、マクロが外部レベルで異なるコードを生成する必要がある場合でした。

についてのあなたの例iterは私にはあまり意味がありません...フォームがボディの前に閉じ(for...)られているとすると、何に拡張する必要がありますか?forリストが単なる構文要件(たとえば、またはで何が起こるdolistdotimes)である場合、それforはマクロではなく、iterそれが受け入れられる唯一の位置であるため、明示的に処理されるものです。

マクロはコード位置でのみチェックされ、 IMOは、有効性のコンテキストで任意のコード位置に配置できる場合にのみ使用する必要があります。それらは抽象化であり、S式のランダムなチャンクではありません。

collectまた、ローカル関数だけでなくマクロである必要があるのはなぜですか?

于 2012-11-24T09:42:39.783 に答える