生成外部代码时避免不必要的符号驻留问题
解决Common Lisp生成远程代码时的包不存在问题
需求背景
我想要编写一个返回未求值CL代码的函数,和宏类似,但返回的代码不会被执行,示例如下:
(defun remote-add (a b) `(+ ,a ,b))
这么做是为了给另一个CL实现生成Lisp例程,但问题在于,当引用当前Lisp环境中不存在、但远程Lisp肯定存在的包时,会触发“包不存在”的错误:
(defun remote-fast-add (a b) `(remote-only:fast-adder ,a ,b)) ;; 报错:不存在REMOTE-ONLY包
目前我用字符串编写代码凑活,但不太满意,想知道有没有更合适的实现技巧,我猜测特殊读取宏可能是一个方向。
可行解决方案
方法1:临时创建远程包后清理
这是最直观的方式——临时创建目标包,生成对应的符号后再删除包,避免污染当前环境:
(defun remote-fast-add (a b) (let ((pkg (or (find-package "REMOTE-ONLY") (make-package "REMOTE-ONLY" :use nil)))) (unwind-protect `(,(intern "FAST-ADDER" pkg) ,a ,b) ;; 仅当包是我们临时创建的时才删除 (unless (find-package "REMOTE-ONLY") (delete-package pkg)))))
生成的符号会带有正确的包限定符,远程Lisp环境可以直接识别,同时当前环境不会留下多余的包。
方法2:手动构造带包限定的符号
如果不想创建临时包,可以直接通过字符串拆分包名和符号名,用intern构造符号(这种方式下符号的symbol-package属性会是nil,但打印时会显示为REMOTE-ONLY::FAST-ADDER,远程环境读取时仍能正确识别为对应包的符号):
(defun remote-fast-add (a b) (let ((sym (intern "FAST-ADDER" "REMOTE-ONLY"))) `(,sym ,a ,b)))
这里intern的第二个参数传字符串时,若包不存在,会返回一个符号,其包属性为nil,但符号名和包前缀信息会被保留,不影响远程环境的解析。
方法3:自定义读取宏简化写法
如果需要频繁生成这类远程代码,可以自定义一个读取宏来跳过当前环境的包检查,直接生成目标符号:
;; 定义#$读取宏,后跟包限定符号的字符串形式 (defun read-remote-symbol (stream char) (declare (ignore char)) (let ((token (read stream t nil t))) (unless (stringp token) (error "Expected string after #$")) (let ((colon-pos (position #\: token))) (if colon-pos (intern (subseq token (1+ colon-pos)) (or (find-package (subseq token 0 colon-pos)) (make-package (subseq token 0 colon-pos) :use nil))) (intern token))))) (set-dispatch-macro-character #\# #\$ #'read-remote-symbol)
使用时只需传入符号的字符串形式:
(defun remote-fast-add (a b) `(#$"remote-only:fast-adder" ,a ,b))
这个宏会自动处理包的创建(临时)和符号生成,后续可以根据需求添加包清理逻辑。
内容的提问来源于stack exchange,提问作者daedsidog
相关产品推荐
相关产品推荐

