9

「ヘルパー関数」を作成するために emacs lisp でマクロを作成しようとしています。</p>

最終的に、私のヘルパー関数は、ここにあるものよりも便利になります。同じことを達成するためのより良い/より直感的な方法があるかもしれないことを認識しています(投稿してください)が、私の基本的な質問は、なぜこれが機能しないのか/何が間違っているのかということです:

(defmacro deftext (functionname texttoinsert)
  `(defun ,(make-symbol (concatenate 'string "text-" functionname)) ()
     (interactive)
     (insert-string ,texttoinsert)))

(deftext "swallow" "What is the flight speed velocity of a laden swallow?")
(deftext "ni" "What is the flight speed velocity of a laden swallow?")

macroexpand の出力を取得してそれを評価すると、マクロで取得するつもりだった対話型関数を取得できますが、マクロが実行されて評価されているように見えても、M-x text-niorを呼び出すことはできませんtext-swallow

4

4 に答える 4

12

これはあなたが望むことをします:

(defmacro deftext (functionname texttoinsert)
  (let ((funsymbol (intern (concat "text-" functionname))))
`(defun ,funsymbol () (interactive) (insert-string ,texttoinsert))))
于 2009-03-19T04:11:50.623 に答える
4

指摘されているように、解決策はinternの代わりに使用することですmake-symbol

同じ名前で複数の独立したシンボルを作成することは可能ですが、そのうちの 1 つのみが、指定された名前の正規のシンボル (つまり、他の場所で参照するときに取得するシンボル) になることができます。

intern指定された名前の正規記号を返します。その名前でインターンされたシンボルがまだ存在しない場合にのみ、新しいシンボルを作成します。これは、任意の名前に対して 1 つのシンボルのみを作成することを意味します1

make-symbol一方、呼び出されるたびに新しいシンボルを作成します。これらはインターンされていないシンボルです。それぞれが完全に有効で機能的なシンボルですが、名前でシンボルを参照したときに表示されるものではありません。

見る:C-hig (elisp) Creating Symbols RET

設定したシンボルを返すためdefun、戻り値をキャプチャして関数として使用することで、何が起こっているかを観察できます。

(defalias 'foo (deftext "ni" "What is the flight speed velocity of a laden swallow?"))
M-x text-ni ;; doesn't work
M-x foo ;; works

または同様に:

(call-interactively (deftext "shrubbery" "It is a good shrubbery. I like the laurels particularly."))

このすべてのトリッキーな部分 - そして、展開されたフォームの評価があなたが望んでいたことをしたのに、マクロがそうしなかった理由 - は、Lisp リーダーが の名前を正確にどのように、いつ(または実際に) 翻訳するということに関係しています。関数をシンボルに変換します。

関数 foo1 を書くと:

(defun foo1 (texttoinsert) (insert-string texttoinsert))

Lisp リーダーはそれをテキストとして読み取り、Lisp オブジェクトに変換します。を使用read-from-stringして同じことを行うことができ、結果の Lisp オブジェクトの印刷された表現を見ることができます。

ELISP> (car (read-from-string "(defun foo1 (texttoinsert) (insert-string texttoinsert))"))
(defun foo1
    (texttoinsert)
  (insert-string texttoinsert))

そのオブジェクト内では、関数名は「foo1」という名前の標準シンボルです。ただし、そこから得られる戻り値は、そのオブジェクトの印刷された表現read-from-stringにすぎず、標準シンボルはその名前だけで表現されていることに注意してください。すべてのシンボルは名前だけで表されるため、印刷された表現ではインターンされたシンボルとインターンされていないシンボルを区別することはできません。

(ちょっと飛ばして、マクロの印刷された展開を評価するとき、これが問題の原因です。印刷されたフォームが Lisp リーダーを通過し、かつてインターンされていないシンボルがインターンされたシンボルになるからです。)

次にマクロに移ると:

(defmacro deffoo2 ()
  `(defun foo2 (texttoinsert) (insert-string texttoinsert)))

ELISP> (car (read-from-string "(defmacro deffoo2 ()
        `(defun foo2 (texttoinsert) (insert-string texttoinsert)))"))
(defmacro deffoo2 nil
  `(defun foo2
       (texttoinsert)
     (insert-string texttoinsert)))

今回は、読者はマクロ定義を Lisp オブジェクトに読み込んでおり、そのオブジェクト内には正規のシンボル がありfoo2ます。これは、オブジェクトを直接検査することで確認できます。

ELISP> (eq 'foo2
           (cadr (cadr (nth 3
            (car (read-from-string "(defmacro deffoo2 ()
             `(defun foo2 () (insert-string texttoinsert)))"))))))
t

したがって、このマクロでは、マクロの呼び出し/展開が発生foo2 する前に、すでに正規のシンボルを処理しています。これは、Lisp リーダーがマクロ自体を読み取るときにそれを確立したためです。以前の単純な関数定義 (関数が定義されたときに関数シンボルが Lisp リーダーによって決定された) とは異なり、マクロが呼び出されて展開されると、Lisp リーダーは使用されません。マクロ展開は、既存の Lisp オブジェクトを使用して実行されます。読み取りは必要ありません。

この例では、関数シンボルは既にマクロに存在しています。これは正規のシンボルであるため(foo2)、コードの他の場所でその関数を呼び出すことができます。(または、定義をインタラクティブにした場合は、 を使用しますM-x foo2。)

最後に、質問から元のマクロに戻ると、Lispリーダーが定義する関数の関数名シンボルに遭遇しないことは明らかです。

ELISP> (car (read-from-string "(defmacro deftext (functionname texttoinsert)
        `(defun ,(make-symbol (concatenate 'string \"text-\" functionname)) ()
           (interactive)
           (insert-string ,texttoinsert)))"))
(defmacro deftext
    (functionname texttoinsert)
  `(defun ,(make-symbol
            (concatenate 'string "text-" functionname))
       nil
     (interactive)
     (insert-string ,texttoinsert)))

代わりに、Lisp リーダーによって生成されたこのオブジェクトには、式,(make-symbol (concatenate 'string "text-" functionname));が含まれています。その逆引用符で囲まれた式は展開時に評価され、その展開によって作成されたオブジェクトの一部となる新しいインターンされていないシンボルが作成されます。

前の例では、結果のオブジェクトには car がdefun(interned)、cadr がfoo1or foo2(どちらも interned) でした。

この最後の例では、オブジェクトは car のdefun(interned) を持っていますが、cadr の uninternedシンボル(concatenate式の結果の名前) を持っています。

そして最後に、そのオブジェクトを印刷すると、インターンされていない関数シンボルの印刷された表現がシンボル名になり、その印刷された表現を評価して読み返すと、代わりに標準シンボルの関数セルが定義されます。

1実際、このunintern関数を使用してシンボルのインターンを解除できます。その後intern、同じ名前を呼び出すと、自然に新しいシンボルが作成されます。しかし、それはこの議論にとって重要ではありません。

于 2014-01-28T02:51:19.717 に答える
3

FWIW、を使用lexical-bindingする場合、マクロを使用する必要はありません:

(defun deftext (functionname texttoinsert)
  (defalias (intern (concat "text-" functionname))
    (lambda ()
      (interactive)
      (insert-string texttoinsert))))
于 2014-01-28T00:00:53.157 に答える