在书的第4章,David B. Lamkins的《成功的Lisp》中,有一个简单的应用程序可以跟踪银行支票。
https://dept-info.labri.fr/~strandh/Teaching/MTP/Common/David-Lamkins/chapter04.html
最后,我们编写了一个宏,它将保存和恢复功能。当我执行读取器函数并且要读取的值是哈希表时,会发生问题。
保存和恢复功能的宏是:
(defmacro def-i/o (writer-name reader-name (&rest vars))
(let ((file-name (gensym))
(var (gensym))
(stream (gensym)))
`(progn
(defun ,writer-name (,file-name)
(with-open-file (,stream ,file-name
:direction :output :if-exists :supersede)
(dolist (,var (list ,@vars))
(declare (special ,@vars))
(print ,var ,stream))))
(defun ,reader-name (,file-name)
(with-open-file (,stream ,file-name
:direction :input :if-does-not-exist :error)
(dolist (,var ',vars)
(set ,var (read ,stream)))))
t)))
这是我的哈希表以及正在发生的事情:
(defvar *payees* (make-hash-table :test #'equal))
(check-check 100.00 "Acme" "Rocket booster T-1000")
CL-USER> *payees*
#<HASH-TABLE :TEST EQUAL :COUNT 0 {25520F91}>
CL-USER> (check-check 100.00 "Acme" "T-1000 rocket booster")
#S(CHECK
:NUMBER 100
:DATE "2020-4-1"
:AMOUNT 100.0
:PAID "Acme"
:MEMO "T-1000 rocket booster")
CL-USER> (def-i/o save-checks charge-checks (*payees*))
T
CL-USER> (save-checks "/home/checks.dat")
NIL
CL-USER> (makunbound '*payees*)
*PAYEES*
CL-USER> (load-checks "/home/checks.dat")
; Evaluation aborted on #<SB-INT:SIMPLE-READER-ERROR "illegal sharp macro character: ~S" {258A8541}>.
在Lispworks中,错误消息是:
Error: subcharacter #\< not defined for dispatch char #\#.
为简单起见,如果执行以下命令,我将收到相同的错误:
CL-USER> (defvar *payees* (make-hash-table :test #'equal))
*PAYEES*
CL-USER> (with-open-file (in "/home/checks.dat"
:direction :input)
(set *payees* (read in)))
; Evaluation aborted on #<SB-INT:SIMPLE-READER-ERROR "illegal sharp macro character: ~S" {23E83B91}>.
有人可以向我解释问题的出处,以及我需要在代码中修复的内容才能使其正常工作。在此先感谢您为我提供的解释和帮助。
无法读取的值以#<
打印,请参见Sharpsign Less-Than-Sign。 Common Lisp并未定义应如何以可读方式打印哈希表(哈希表没有读取器语法):
* (make-hash-table)
#<HASH-TABLE :TEST EQL :COUNT 0 {1006556E53}>
本书中的示例仅适合与先前显示的bank示例一起使用,并且不打算用作通用序列化机制。
根据您的需要,有很多方法可以打印哈希表并读回哈希表,但是没有默认表示形式。有关存储任意对象的库,请参见cl-store
。
例如,您可以编写一个计算为等效哈希表的表单;让我们定义一个名为dump
的泛型函数,以及一个仅按原样返回对象的默认方法:
(defgeneric dump (object)
(:method (object) object))
给定哈希表,我们可以将其序列化为plist(列表中键/值元素的序列),同时在键和值上调用dump
,以防我们的哈希表包含哈希-表格:
(defun hash-table-plist-dump (hash &aux plist)
(with-hash-table-iterator (next hash)
(loop
(multiple-value-bind (some key value) (next)
(unless some
(return plist))
(push (dump value) plist)
(push (dump key) plist)))))
我们本来可以将hash-table-plist
用于亚历山大系统。
(ql:quickload :alexandria)
以上等同于:
(defun hash-table-plist-dump (hash)
(mapcar #'dump (alexandria:hash-table-plist hash)))
然后,我们可以将dump
专用于哈希表:
(defmethod dump((hash hash-table))(环用于(initarg访问器)在'((:test hash-table-test)(:size哈希表大小)(:rehash-size哈希表-rehash-size)(:rehash-threshold哈希表-rehash-threshold))将initarg收集到args中收集(quote ,(funcall accessor hash)) into args
finally (return
(alexandria:with-gensyms (h k v)
(循环:with,h:=(make-hash-table,@ args):对于(,k,v):on(列表,@(hash-table-plist-dump hash)):by#'cddr:do(setf(gethash,k,h),v):finally(return,h))))))))))>
循环的第一部分计算所有哈希表属性,例如要使用的哈希函数的类型或rehash-size,并构建args
,即以相同的值调用make-hash-table
的参数列表。
在finally
子句中,我们构建一个loop
表达式(请参阅反引号),该表达式首先分配哈希表,然后根据哈希的当前值填充该表,最后返回新的哈希。请注意,生成的代码不依赖于alexandria
例如:
CL-USER> (alexandria:plist-hash-table '("abc" 0 "def" 1 "ghi" 2 "jkl" 3) :test #'equal) #<HASH-TABLE :TEST EQUAL :COUNT 4 {100C91F8C3}>
转储:
CL-USER> (dump *) (LOOP :WITH #:H603 := (MAKE-HASH-TABLE :TEST 'EQUAL :SIZE '14 :REHASH-SIZE '1.5 :REHASH-THRESHOLD '1.0) :FOR (#:K604 #:V605) :ON (LIST "jkl" 3 "ghi" 2 "def" 1 "abc" 0) :BY #'CDDR :DO (SETF (GETHASH #:K604 #:H603) #:V605) :FINALLY (RETURN #:H603))
生成的数据也是有效的Lisp代码,请对其进行评估:
CL-USER> (eval *) #<HASH-TABLE :TEST EQUAL :COUNT 4 {100CD5CE93}>
产生的哈希值是原始值的
equalp
:
CL-USER> (equalp * ***)
T