指摘されているように、解決策は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 がfoo1
or foo2
(どちらも interned) でした。
この最後の例では、オブジェクトは car のdefun
(interned) を持っていますが、cadr の uninternedシンボル(concatenate
式の結果の名前) を持っています。
そして最後に、そのオブジェクトを印刷すると、インターンされていない関数シンボルの印刷された表現がシンボル名になり、その印刷された表現を評価して読み返すと、代わりに標準シンボルの関数セルが定義されます。
1実際、このunintern
関数を使用してシンボルのインターンを解除できます。その後intern
、同じ名前を呼び出すと、自然に新しいシンボルが作成されます。しかし、それはこの議論にとって重要ではありません。