如何通过宏以可移植方式程序化遮蔽Common Lisp符号?
自动遮蔽Common Lisp符号的宏实现问题
需求
编写一个宏,自动遮蔽common-lisp包中已存在的同名符号,同时完成函数定义与导出操作,无需手动维护遮蔽和导出的符号列表。
现有包定义
(in-package :cl-user) (uiop:define-package :foo-package (:nicknames :foo) (:use :common-lisp) (:shadow #:defun)) (in-package :foo)
测试函数
(defun make-string (length init) (cl:make-string length :initial-element init))
失败的尝试及问题
首次尝试的宏
(cl:defmacro defun (name arglist &optional docstring &rest body) `(progn (when (symbol-package ',name) (shadow ',name)) (cl:defun ,name ,arglist ,docstring ,@body) (export '(,name))))
- 问题:SBCL中触发包锁违反,
shadow执行时机晚于cl:defun的编译检查,即使包裹eval-when也无法解决。
第二种尝试的宏
(eval-when (:compile-toplevel :load-toplevel :execute) (cl:defmacro defun (name args &optional docstring &rest body) (macrolet ((export-it (name args &optional docstring &rest body) `(progn (when (symbol-package ',name) (shadow ',name)) (cl:defun ,name ,args ,docstring ,@body) (export '(,name) *package*)))) `(export-it ,name ,args ,docstring ,@body))))
- 错误提示:
debugger invoked on a UNBOUND-VARIABLE @10039FFD22 in thread
#<THREAD "main thread" RUNNING {10044982F3}>: The variable MAKE-STRING is unbound.
手动在REPL执行(shadow 'make-string)后可正常重定义导出,说明核心问题是宏的时机控制错误。
解决方案
核心思路是让shadow在**宏展开阶段(编译时)**执行,而非运行时。修改后的宏如下:
(cl:defmacro defun (name arglist &optional docstring &rest body) ;; 宏展开时直接检查并执行遮蔽操作 (when (find-symbol (symbol-name name) :common-lisp) (shadow name *package*)) ;; 生成函数定义与导出代码 `(progn (cl:defun ,name ,arglist ,@(if docstring (list docstring) nil) ,@body) (export ',name)))
关键说明
- 时机控制:在宏展开阶段调用
find-symbol检查common-lisp包中是否存在同名符号,若存在则立即执行shadow,确保编译器处理cl:defun时符号已被遮蔽,避免包锁问题。 - 准确性:用
find-symbol替代symbol-package,直接检查目标包(common-lisp)中是否有目标符号,逻辑更准确。 - 简洁性:生成的代码仅保留函数定义和导出操作,无需再处理遮蔽逻辑。
验证
使用测试函数验证:
(defun make-string (length init) (cl:make-string length :initial-element init))
编译运行无包锁错误,foo:make-string可正常调用,cl:make-string保持原有功能。
内容的提问来源于stack exchange,提问作者myname
相关产品推荐
相关产品推荐

