为什么可以';t CLISP调用某些具有未知名称的函数?
我已经编写了一个特殊的解析器生成器,它创建了将一个旧的、鲜为人知的7位字符集转换为unicode的代码。对解析器生成器的调用扩展为一组为什么可以';t CLISP调用某些具有未知名称的函数?,lisp,common-lisp,sbcl,clisp,Lisp,Common Lisp,Sbcl,Clisp,我已经编写了一个特殊的解析器生成器,它创建了将一个旧的、鲜为人知的7位字符集转换为unicode的代码。对解析器生成器的调用扩展为一组defuns,包含在progn中,然后进行编译。我只想将生成的defuns中的一个公开给系统的其余部分——顶层;所有其他函数都是解析器的内部函数,只能在顶级解析器的动态范围内调用。因此,生成的其他defun具有不相关的名称(使用gensym创建)。该策略在SBCL中运行良好,但我最近首次在CLISP中对其进行了测试,结果出现如下错误: *** - FUNCALL:
defun
s,包含在progn
中,然后进行编译。我只想将生成的defun
s中的一个公开给系统的其余部分——顶层;所有其他函数都是解析器的内部函数,只能在顶级解析器的动态范围内调用。因此,生成的其他defun
具有不相关的名称(使用gensym
创建)。该策略在SBCL中运行良好,但我最近首次在CLISP中对其进行了测试,结果出现如下错误:
*** - FUNCALL: undefined function #:G16985
CLISP似乎无法处理名称不相关的函数。(有趣的是,系统编译时没有问题。)EDIT:在大多数情况下,它似乎可以处理名称不相关的函数。见下面Rörd的答案
我的问题是:这是CLISP的问题,还是某些实现(如SBCL)碰巧克服了公共Lisp的局限性
编辑:
例如,顶级生成函数(称为parse
)的宏扩展有如下表达式:
(PRINC (#:G75735 #:G75731 #:G75733 #:G75734) #:G75732)
(defun #1=#:fact (n &optional (acc 1))
(if (zerop n) acc
(#1# (1- n) (* acc n))))
计算此表达式(通过调用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的两个实例绝对是同一个符号——而不是两个同名的不同符号。正如我所说,这适用于SBCL,但不适用于CLISP
编辑:
因此,用户Joshua Taylor指出,这是由于。我希望我正确处理了这个问题。对我来说,它在CLISP中工作 我试着这样做:使用宏创建一个具有GENSYM ed名称的函数
(defmacro test ()
(let ((name (gensym)))
`(progn
(defun ,name (x) (* x x))
',name)))
现在我可以得到名称
(setf x(test))
并将其命名为(funcall x 2)
您没有显示给出错误的行,因此我只能猜测,但据我所知,唯一可能导致这个问题的是,当你试图调用它时,你指的是符号的名称,而不是符号本身
如果您引用的是符号本身,那么lisp实现所要做的就是查找该符号的符号函数
。不管是否被拘留都不重要
请问您为什么没有考虑另一种隐藏函数的方法,即标签
语句或在只导出一个外部函数的新包中定义函数
编辑:以下示例是从与CLISP提示符的交互中复制的
正如您所看到的,调用由gensym命名的函数可以正常工作
[1]> (defmacro test ()
(let ((name (gensym)))
`(progn
(defun ,name () (format t "Hello!"))
(,name))))
TEST
[2]> (test)
Hello!
NIL
可能是您试图调用函数的代码在defun
之前得到评估?如果宏扩展中除了各种defun
s之外还有任何代码,那么它可能取决于首先评估的内容的实现,因此SBCL和CLISP的行为可能会有所不同,而不会违反任何标准
编辑2:一些进一步的调查表明,CLISP的行为取决于是直接解释代码还是先编译后解释代码。您可以通过在CLISP中直接load
ing一个Lisp文件,或者首先在其上调用compile file
,然后load
ing FASL来查看差异
您可以通过查看CLISP提供的第一次重启来了解发生了什么。它的意思类似于“输入一个要使用的值,而不是(FDEFINITION'#:G3219)”,因此对于编译代码,CLISP引用符号并按名称引用它
这种行为似乎符合标准。可以在HyperSpec中找到以下定义:
功能指示符。函数的指示符;也就是说,一个表示函数的对象,它是以下对象之一:符号(表示在全局环境中由该符号命名的函数)或函数(表示自身)。如果一个符号用作函数指示符,但它没有作为函数的全局定义,或者它有作为宏或特殊形式的全局定义,则结果是未定义的。另请参见扩展功能指示符
我认为一个非预期符号与“一个符号被用作函数指示符,但它没有作为函数的全局定义”的情况相匹配,用于未指定的结果
编辑3:(我可以同意,我不确定CLISP的行为是否是一个bug。有经验的标准术语的细节的人应该判断这一点,归结到一个未被嵌入的符号的功能单元,即一个不能被名字引用的符号,只有直接持有符号对象,才会被考虑。(是否定义了“全球定义”)
无论如何,下面是一个示例解决方案,它通过将符号放入一次性软件包中来解决CLISP中的问题,避免了不感兴趣的符号问题:
(defmacro test ()
(let* ((pkg (make-package (gensym)))
(name (intern (symbol-name (gensym)) pkg)))
`(progn
(defun ,name () (format t "Hello!"))
(,name))))
(test)
编辑4:正如约书亚·泰勒(Joshua Taylor)在对问题的评论中所指出的,这似乎是(10岁)的一个案例
我已经测试了该bug报告中建议的两种解决方法,发现用
本地替换progn
实际上没有帮助,但用let()
替换它确实有帮助。您可以很确定地定义名称为不相关符号的函数。例如:
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)
但是,每次读取此类表单时,语法都会引入一个新符号:
#:引入名称为symbol name的非预期符号。每次遇到此语法时,都会创建一个不同的非预期符号。符号名称必须为
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))))
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))))
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
(defmacro foo (arg)
(let ((g (gensym))
(literal '(blah ,g ,g ,arg)))
...))
(defun bar ()
(foo 42))