从你的问题中并不清楚你想做什么。
第一个重要提示:您的代码已严重损坏。它已损坏,因为您仅在编译时定义 +global-vector-constant+ ,但稍后再引用它。如果您编译此文件,然后将该编译后的文件加载到冷映像中,则会出现错误。
在处理此类事情时绝对关键,以确保您的代码能够在冷的 Lisp 中编译。常驻环境的经典问题之一(与 Interlisp-D 的方式相比,CL 并不是真的)最终会导致无法冷构建的系统:我很确定我工作了好几个多年以来,没有人知道如何冷构建的 Interlisp-D 系统输出。
如果你想要的是一个对象(例如一个数组),它的初始值是在编译时计算出来的,然后被当作一个字面量处理,那么答案通常是一个宏:宏就是函数在编译时完成它们的工作,因此宏可以扩展为文字。此外,您希望成为文字的对象必须是 externalizable (这意味着“可以转储到已编译文件中”)并且其中涉及的任何内容在编译时都是已知的。一些类的实例默认是可外部化的,另外一些类的实例可以通过用户代码实现外部化,还有一些根本不能外部化(例如实例函数)。
在很多简单的情况下,比如你给出的那个,如果我理解的话,你并不真的需要一个宏,事实上你几乎总是可以不用宏,尽管它可能会使你的代码更容易了解您是否确实使用了一个。
这是一个简单的例子:许多数组都是可外部化的,如果它们的元素是
(defparameter *my-strings*
#(("0-l" . "0-r")
("1-l" . "1-r")))
这意味着*my-strings* 将被绑定到一个由字符串组成的字面量数组。
一个更有趣的情况是元素是,例如结构。嗯,结构也是可以外部化的,所以我们可以这样做。事实上,仍然可以避免使用宏,尽管它现在变得有点嘈杂。
(eval-when (:compile-toplevel :load-toplevel :execute)
(defstruct foo
l
r))
(defparameter *my-strings*
#(#s(foo :l "0-l" :r "0-r")
#s(foo :l "1-l" :r "1-r")))
请注意,以下将不起作用:
(defstruct foo
l
r)
(defparameter *my-strings*
#(#s(foo :l "0-l" :r "0-r")
#s(foo :l "1-l" :r "1-r")))
它不会起作用,因为在编译时,您正试图将尚未定义的结构的实例外部化(但如果 Lisp 不冷,它可能将起作用,并且您甚至可以重新加载您以这种方式制作的编译文件)。同样,在这种情况下,您可以通过确保在加载具有 defparameter 的文件之前编译和加载定义 foo 结构的文件来避免在更大的系统中使用 eval-when。
即使在更复杂的情况下,您也可以使用宏进行转义。例如,对于许多通常不可外部化的对象,您可以教系统如何将它们外部化,然后使用 #. 将对象拼接为文字:
(eval-when (:compile-toplevel :load-toplevel :execute)
;; Again, this would be in its own file in a bigger system
(defclass string-table-wrapper ()
((strings)
(nstrings :initform 0)))
(defmethod initialize-instance :after ((w string-table-wrapper)
&key (strings '()))
(let ((l (length strings)))
(when l
(with-slots ((s strings) (n nstrings)) w
(setf s (make-array l :initial-contents strings)
n l)))))
(defmethod make-load-form ((w string-table-wrapper) &optional environment)
(make-load-form-saving-slots w :slot-names '(strings nstrings)
:environment environment))
) ;eval-when
(defgeneric get-string (from n)
(:method ((from string-table-wrapper) (n fixnum))
(with-slots (strings nstrings) from
(assert (< -1 n nstrings )
(n)
"bad index")
(aref strings n))))
(defparameter *my-strings*
#.(make-instance 'string-table-wrapper
:strings '("foo" "bar")))
当然,请注意,虽然*my-strings* 的值是一个字面量,但在加载时运行代码来重建该对象。但情况总是如此:只是在这种情况下,您必须定义需要运行的代码。除了使用make-load-form-saving-slots,您还可以自己完成此操作,例如通过以下方式:
(defmethod make-load-form ((w string-table-wrapper) &optional environment)
(declare (ignore environment))
(if (slot-boundp w 'strings)
(values
`(make-instance ',(class-of w))
`(setf (slot-value ,w 'strings)
',(slot-value w 'strings)
(slot-value ,w 'nstrings)
,(slot-value w 'nstrtrings)))
`(make-instance ',(class-of w))))
但是make-load-form-saving-slots 要容易得多。
这是一个示例,其中宏可能最起码使阅读代码更容易。
假设您有一个从文件中读取字符串数组的函数,例如:
(defun file-lines->svector (file)
;; Needs CL-PPCRE
(with-open-file (in file)
(loop
with ltw = (load-time-value
(create-scanner '(:alternation
(:sequence
:start-anchor
(:greedy-repetition 1 nil
:whitespace-char-class))
(:sequence
(:greedy-repetition 1 nil
:whitespace-char-class)
:end-anchor)))
t)
for nlines upfrom 0
for line = (read-line in nil)
while line
collect (regex-replace-all ltw line "") into lines
finally (return (make-array nlines :initial-contents lines)))))
那么,如果这个函数在宏扩展时可用,你可以写这个宏:
(defmacro file-strings-literal (file)
(check-type file (or string pathname) "pathname designator")
(file-lines->svector file))
现在我们可以创建一个字符串的文字向量:
(defparameter *fl* (file-strings-literal "/tmp/x"))
但是你完全可以这样做:
(defparameter *fl* #.(file-lines->svector "/tmp/x"))
这会做同样的事情,但稍早一些(在读取时,而不是在宏扩展/编译时)。所以这真的一无所获。
但你也可以这样做这个:
(defmacro define-stringtable (name file &optional (doc nil docp))
`(defparameter ,name ,(file-lines->svector file)
,@(if docp (list doc) nil)))
现在你的代码看起来像
(define-stringtable *st* "my-stringtable.dat")
这实际上是一项重大改进。
最后请注意,在file-lines->svector 中,load-time-value 用于在加载时仅创建一次扫描仪,这是一个相关的技巧。