在我的程序中,我有常量字符串,这些值在编译时是已知的。对于每个偏移量,当前有两个相关联的字符串。我首先编写了以下代码:
(eval-when (:compile-toplevel :load-toplevel :execute) ;; BLOCK-1
(defstruct test-struct
str-1
str-2))
(eval-when (:compile-toplevel) ;; BLOCK-2
(defparameter +GLOBAL-VECTOR-CONSTANT+ nil) ;; ITEM-1
(let ((vector (make-array 10
:initial-element (make-test-struct)
:element-type 'test-struct)))
(setf (test-struct-str-1 (aref vector 0)) "test-0-1")
(setf (test-struct-str-2 (aref vector 0)) "test-0-2")
(setf +GLOBAL-VECTOR-CONSTANT+ vector)))
(format t "[~A]~%" (test-struct-str-1 (elt +GLOBAL-VECTOR-CONSTANT+ 0)))
(format t "[~A]~%" (test-struct-str-2 (elt +GLOBAL-VECTOR-CONSTANT+ 0)))这似乎有效,因为它返回以下内容:
[test-2-1]
[test-2-2]在BLOCK-1中,定义了包含数据的struct,用于compile-time、load-time和execute-time。在BLOCK-2中,在compile-time上执行创建向量和设置值的代码。
但我有以下忧虑:
这些字符串存储在structure
offset ((aref vector 0)、(aref vector 1)等)。当我将< code >D21设置为BLOCK-1而不是BLOCK-2时,出现了d24中的错误,而我不理解H 225F 226>时,出现了错误。在Common Lisp中定义复杂常数的惯用方法是什么?
发布于 2021-12-27 13:01:55
从你的问题上看不清楚你想做什么。
第一个重要注意事项:您的代码被严重破坏了。因为您只在编译时定义了+global-vector-constant+,但是引用它的时间比它晚,所以它失败了。如果编译此文件,然后将编译后的文件加载到冷映像中,则会出现错误。
在处理这样的事情时,确保代码在冷Lisp中编译是绝对关键的。与Interlisp的方式相比,常驻环境的一个经典问题(与Interlisp的方式相比)是无法冷构建的系统:我很确定我在Interlisp系统中工作了几年,没有人知道如何冷构建。
如果您想要的是一个对象(例如,一个数组),它的初始值在编译时计算,然后作为一个文字来处理,那么对它的回答通常是宏:宏正是在编译时执行其工作的函数,因此宏可以扩展为文字。此外,您想要成为文本的对象必须是可外化的(这意味着“可以在编译后的文件中转储”),并且在编译时就知道了其中涉及的任何事情。某些类的实例在默认情况下是可外化的,其他一些类的实例可以通过用户代码进行外部化,而有些类的实例则完全不能外部化(例如函数)。
在很多简单的情况下,比如您给出的例子,如果我理解它,您实际上并不需要宏,而且实际上您几乎总是可以不使用宏,尽管如果使用宏,代码可能会变得更容易理解。
下面是一个简单的例子:如果许多数组的元素是
(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不冷,您甚至可以重新加载所编译的文件)。同样,在这种情况下,通过确保在加载带有eval-when的文件之前编译和加载定义foo结构的文件,可以避免在更大的系统中使用defparameter。
即使在更复杂的情况下,也可以使用宏来逃避。例如,对于许多通常不可外化的对象,您可以教系统如何将它们外部化,然后使用#.将对象合并为文字。
(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准确地用于在加载时创建扫描仪一次,这是一个相关的技巧。
发布于 2021-12-26 16:17:09
首先,可以将let代码简化为
(defparameter +global-vector-constant+
(let ((vector ...))
...
vector))第二,你也可以
(defparameter +global-vector-constant+
(make-array 10 :element-type 'test-struct :initial-content
(cons (make-test-struct :str-1 "test-0-1" :str-2 "test-0-2")
(loop :repeat 9 :collect (make-test-struct)))))请注意,:element-type 'test-struct的好处通常仅限于代码自文档(参见upgraded-array-element-type)。
https://stackoverflow.com/questions/70486848
复制相似问题