4

私は、古くてほとんど知られていない 7 ビット文字セットを Unicode に変換するコードを作成するアドホック パーサー ジェネレーターを作成しました。パーサー ジェネレーターへの呼び出しは、 でdefun囲まれた一連の に展開され、prognコンパイルされます。defun生成されたs の 1 つ (トップレベルのもの) だけをシステムの残りの部分に公開したいと考えています。他のすべてはパーサーの内部にあり、最上位のパーサーの動的スコープ内からのみ呼び出されます。したがって、defun生成された他の にはインターンされていない名前があります ( で作成gensym)。この戦略は SBCL では問題なく機能しますが、最近 CLISP で初めてテストしたところ、次のようなエラーが発生しました。

*** - FUNCALL: undefined function #:G16985

CLISP はインターンされていない名前の関数を処理できないようです。(興味深いことに、システムは問題なくコンパイルされました。)編集:ほとんどの場合、インターンされていない名前の関数を処理できるようです。以下のRördによる回答を参照してください。

私の質問は次のとおりです: これは CLISP の問題ですか、それとも特定の実装 (例: SBCL) がたまたま克服した Common Lisp の制限ですか?

編集:

たとえば、トップレベルで生成された関数 ( と呼ばれる) のマクロ展開parseには、次のような式があります。

(PRINC (#:G75735 #:G75731 #:G75733 #:G75734) #:G75732)

この式を ( を呼び出してparse) 評価すると、関数がまったく同じマクロ展開内で明確に定義されていても、上記のようなエラーが発生します。

(DEFUN #:G75735 (#:G75742 #:G75743 #:G75744) (DECLARE (OPTIMIZE (DEBUG 2)))
 (DECLARE (LEXER #:G75742) (CONS #:G75743 #:G75744))
 (MULTIPLE-VALUE-BIND (#:G75745 #:G75746) (POP-TOKEN #:G75742)
 ...

#:G75735 の 2 つのインスタンスは、間違いなく同じシンボルです。同じ名前の 2 つの異なるシンボルではありません。前述したように、これは SBCL では機能しますが、CLISP では機能しません。

編集:

SO ユーザーの Joshua Taylor は、これは長きにわたる CLISP のバグによるものであると指摘しています。

4

5 に答える 5

5

エラーが発生する行の 1 つが表示されていないため、推測することしかできませんが、私が見る限り、この問題を引き起こす可能性がある唯一のことは、シンボルの代わりにシンボルの名前を参照していることです。それを呼び出そうとするときのシンボル自体。

シンボル自体を参照している場合、Lisp の実装で行う必要があるのは、そのシンボルの を検索することだけですsymbol-function。抑留されているかどうかは問題ではありません。

labels関数を非表示にする別の方法、つまり、1 つの外部関数のみをエクスポートする新しいパッケージ内でステートメントまたは関数を定義することを検討しなかった理由をお尋ねしてもよろしいですか?

EDIT : 次の例は、CLISP プロンプトとの対話から文字どおりコピーされています。

ご覧のとおり、gensym によって名前が付けられた関数の呼び出しは期待どおりに機能しています。

[1]> (defmacro test ()
(let ((name (gensym)))
`(progn
(defun ,name () (format t "Hello!"))
(,name))))
TEST
[2]> (test)
Hello!
NIL

関数を呼び出そうとするコードがdefun?の前に評価される可能性があります。さまざまな 以外のマクロ展開にコードがある場合、defun何が最初に評価されるかは実装に依存する可能性があるため、SBCL と CLISP の動作は、標準に違反することなく異なる場合があります。

EDIT 2 : さらに調査すると、コードが直接解釈されるか、最初にコンパイルされてから解釈されるかによって、CLISP の動作が異なることがわかります。loadCLISP で Lisp ファイルを直接実行するか、最初に Lisp ファイルを呼び出しcompile-fileてからloadFASL を実行することで、違いを確認できます。

CLISP が提供する最初の再起動を見ると、何が起こっているかがわかります。「(FDEFINITION '#:G3219)の代わりに使用する値を入力してください」のようなものです。そのため、コンパイルされたコードの場合、CLISP はシンボルを引用符で囲み、名前で参照します。

ただし、この動作は標準に準拠しているようです。次の定義は、HyperSpec に記載されています。

関数指定子 n. 関数の指定子。つまり、関数を示すオブジェクトで、シンボル (グローバル環境でそのシンボルによって名前が付けられた関数を示す) または関数 (それ自体を示す) のいずれかです。シンボルが関数指定子として使用されているが、関数としてグローバル定義を持っていない場合、またはマクロまたは特殊な形式としてグローバル定義を持っている場合、結果は未定義です。拡張機能指定子も参照してください。

インターンされていないシンボルは、「シンボルは関数指定子として使用されていますが、関数としてのグローバル定義を持っていません」という不特定の結果に一致すると思います。

編集 3 : (CLISP の動作がバグであるかどうかわからないことに同意できます。標準の用語の詳細に詳しい人がこれを判断する必要があります。インターンされていないシンボルの関数セル-つまりシンボルオブジェクトを直接保持することによってのみ、名前で参照できないシンボル - 「グローバル定義」と見なされるかどうか)

とにかく、シンボルを使い捨てパッケージにインターンし、インターンされていないシンボルの問題を回避することにより、CLISP の問題を解決するソリューションの例を次に示します。

(defmacro test ()
  (let* ((pkg (make-package (gensym)))
         (name (intern (symbol-name (gensym)) pkg)))
    `(progn
       (defun ,name () (format t "Hello!"))
       (,name))))

(test)

EDIT 4 : Joshua Taylor が質問へのコメントで指摘しているように、これは (10 歳の) CLISP バグ #180の場合のようです。

そのバグ レポートで提案されている両方の回避策をテストしたところ、 を に置き換えてprognlocally実際には役に立たないが、 に置き換えても効果があることがわかりましたlet ()

于 2013-10-03T14:54:57.357 に答える
2

名前がインターンされていないシンボルである関数を確実に定義できます。例えば:

CL-USER> (defun #:foo (x)
           (list x))
#:FOO
CL-USER> (defparameter *name-of-function* *)
*NAME-OF-FUNCTION*
CL-USER> *name-of-function*
#:FOO
CL-USER> (funcall *name-of-function* 3)
(3)

ただし、sharpsign コロン構文は、そのようなフォームが読み取られるたびに新しい記号を導入します。

#: 名前が symbol-name であるインターンされていないシンボルを導入します。この構文が検出されるたびに、個別のインターンされていないシンボルが作成されます。symbol-name は、パッケージ プレフィックスのないシンボルの構文を持っている必要があります。

これは、次のようなものであっても

CL-USER> (list '#:foo '#:foo)
;=> (#:FOO #:FOO) 

は同じ印刷表現を示していますが、実際には次のように 2 つの異なるシンボルがあります。

CL-USER> (eq '#:foo '#:foo)
NIL

これは、そのような関数を呼び出そうとすると、#:その関数に名前を付けるシンボルの名前を入力すると、問題が発生することを意味します。

CL-USER> (#:foo 3)
; undefined function #:foo error

したがって、最初に示した例のような方法で関数を呼び出すことはできますが、最後の例ではできません。印刷された表現では、これが起こっているように見えるため、これはちょっと混乱する可能性があります。たとえば、階乗関数は次のように記述できます。

(defun #1=#:fact (n &optional (acc 1))
  (if (zerop n) acc
      (#1# (1- n) (* acc n))))

特別なリーダー表記法を使用して、#1=#:fact後で#1#同じシンボルを参照します。ただし、同じフォームを印刷するとどうなるか見てください。

CL-USER> (pprint '(defun #1=#:fact (n &optional (acc 1))
                    (if (zerop n) acc
                        (#1# (1- n) (* acc n)))))

(DEFUN #:FACT (N &OPTIONAL (ACC 1))
  (IF (ZEROP N)
      ACC
      (#:FACT (1- N) (* ACC N))))

印刷された出力をコピーして定義として貼り付けようとすると、 が 2 回出現すると、リーダーは「FACT」という名前の2 つの#:FACTシンボルを作成し、関数は機能しません (さらに undefined になる可能性があります)。関数の警告):

CL-USER> (DEFUN #:FACT (N &OPTIONAL (ACC 1))
           (IF (ZEROP N)
               ACC
               (#:FACT (1- N) (* ACC N))))

; in: DEFUN #:FACT
;     (#:FACT (1- N) (* ACC N))
; 
; caught STYLE-WARNING:
;   undefined function: #:FACT
; 
; compilation unit finished
;   Undefined function:
;     #:FACT
;   caught 1 STYLE-WARNING condition
于 2013-10-03T15:37:24.757 に答える
0

問題が解決することを願っています。私にとっては CLISP で動作します。

私は次のように試しました: GENSYM 化された名前を持つ関数を作成するためのマクロを使用します。

(defmacro test ()  
  (let ((name (gensym)))  
    `(progn  
       (defun ,name (x) (* x x))  
       ',name)))

これで、名前(setf x (test))を取得して呼び出すことができます(funcall x 2)

于 2013-10-03T14:29:32.970 に答える
0

はい、意図しないシンボルの名前を持つ関数を定義することはまったく問題ありません。問題は、インターンされていないシンボルを名前でフェッチできないため、それらを「名前で」呼び出すことができないことです (つまり、「インターンされていない」とは本質的に意味します)。

シンボルをフェッチできるようにするには、インターンされていないシンボルを何らかのデータ構造に格納する必要があります。または、定義された関数を何らかのデータ構造に格納します。

于 2013-10-03T15:44:55.467 に答える
0

驚いたことに、CLISP バグ 180は実際には ANSI CL 準拠のバグではありません。それだけでなく、明らかに、ANSI Common Lisp 自体はこの点で非常に壊れているため、prognベースの回避策でさえ実装の礼儀です。

Common Lisp はコンパイルを目的とした言語であり、コンパイルは、コンパイルされたファイルに配置され、後でロードされるオブジェクト (「外部化された」オブジェクト) の識別に関する問題を引き起こします。ANSI Common Lisp では、コンパイルされたファイルから再現されたリテラル オブジェクトは、元のオブジェクトにのみている必要があります。( CLHS 3.2.4コンパイル済みファイル内のリテラル オブジェクト)。

まず、類似性の定義 ( 3.2.4.2.2類似性の定義 ) によると、インターンされていないシンボルのルールは、類似性は名前に基づくというものです。インターンされていないシンボルを含むリテラルを使用してコードをコンパイルすると、コンパイルされたファイルをロードすると、(必然的に) 同じオブジェクトではない、類似したシンボルが取得されます: 同じ名前を持つシンボル。

同じインターンされていないシンボルが、ファイルとしてコンパイルされる 2 つの異なるトップレベル フォームに挿入された場合はどうなるでしょうか? ファイルがロードされたとき、これら 2 つは少なくとも互いに似ていますか? いいえ、そのような要件はありません。

しかし、さらに悪いことに、同じインターン化されていない同じシンボルが同じ形式で 2 回出現する場合、それらの相対的な同一性が保持されるように外部化する必要もありません。そのオブジェクトの再ロードされたバージョンは同じシンボルを持つ必要があります。元があったすべての場所にあるオブジェクト。実際、類似性の定義には、循環構造と部分構造の共有を維持するための規定は含まれていません。コンパイルされたファイルのリテラルとして のようなリテラルがある場合、これを'#1=(a b . #1#)元のグラフ構造と同じグラフ構造を持つ循環オブジェクトとして再現する必要はないようです (グラフ同型)。コンスの類似性ルールは、単純な再帰として与えられます。2 つのコンスは、それぞれcarの s とcdrは似ています。(ルールは円形のオブジェクトに対しても評価できません。終了しません)。

上記が機能するのは、実装が仕様で必要とされているものを超えているためです。それらは ( 3.2.4.3 類似ルールの拡張)と一致する拡張を提供しています。

したがって、純粋に ANSI CL によると、少なくともいくつかの方法で、コンパイル済みファイルで gensyms を含むマクロを使用することは期待できません。次のようなコードで表現された期待は、仕様に違反します。

(defmacro foo (arg)
   (let ((g (gensym))
         (literal '(blah ,g ,g ,arg)))
      ...))

(defun bar ()
  (foo 42))

このbar関数には、gensym が 2 つ挿入されたリテラルが含まれています。これは、コンスとシンボルの類似規則に従って、2 番目と 3 番目の位置に同じオブジェクトが 2 回出現するリストとして再現する必要はありません。

上記が期待どおりに機能する場合、それは「類似性ルールの拡張」によるものです。

したがって、「なぜ CLISP を使えないのか ...」という質問に対する答えは、CLISP はリテラル形式のグラフ構造を保持する類似性の拡張を提供しますが、コンパイルされたファイル全体ではなく、個々のファイル内でのみ行うということです。そのファイル内の最上位アイテム。(*print-circle*個々のアイテムを発行するために使用します。) バグは、CLISP がユーザーが想像できる最善の動作、または少なくとも他の実装によって示されるより良い動作に準拠していないことです。

于 2015-12-08T20:34:08.113 に答える