我需要从数据库中读取一串Common Lisp对象。该对象应该是一个包含两个double-float元素的列表; “(1.0d0 2.0d0)”例如:
(let* ((str "(1d0 2d0)")
(off (read-from-string str)))
(destructuring-bind (x y)
off (list x y)))
但是有些条件是字符串格式不正确吗?例如,查询失败,或者该对象不存在。代码将给出arg-count-error:
error while parsing arguments to DESTRUCTURING-BIND:
too few elements in
()
to satisfy lambda list
(X Y):
exactly 2 expected, but got 0
[Condition of type SB-KERNEL::ARG-COUNT-ERROR]
我必须使用以下代码进行类型检查。
(let* ((str "(1d0 2d0)")
(off (read-from-string str)))
(destructuring-bind (x y)
(if (and off
(typep off 'list)
(= 2 (length off)))
off
(list 0d0 0d0)) (list x y)))
如果str格式不正确,代码段将返回默认值。 :(0.0d0 0.0d0);
我做得对吗? 有没有更好的方法来避免这个错误?
答案 0 :(得分:4)
我对lisp相对较新,所以你最好等老狗给你解决方案。我将如何重构它。
对于初学者,您还可以检查列表的两个元素是否为
类型的浮点数(floatp element)
这将使您的代码段看起来像这样
(let* ((str "(1d0 2d0)")
(off (read-from-string str)))
(destructuring-bind (x y)
(if (and off
(typep off 'list)
(= 2 (length off)
(every #'floatp off)) ; here is the new code
off
(list 0d0 0d0)) (list x y)))
然而,眼睛已经很难了。让它成为一个谓词。我会假设你想要的是一个坐标。您将使变量名称与您的应用程序域匹配。
(defun coord-p (coord)
(and coord
(typep coord 'list)
(= 2 (length coord)
(every #'floatp coord)))
现在该代码段应如下所示:
(let* ((str "(1d0 2d0)")
(coord (read-from-string str)))
(destructuring-bind (x y)
(if (coord-p coord)
coord
(list 0d0 0d0))
(list x y)))
让我们简化destucturing-bind
(defun ensure-coord (coord)
(if (coord-p coord)
coord
(list 0d0 0d0)))
现在该代码段应如下所示:
(let* ((str "(1d0 2d0)")
(coord (read-from-string str)))
(destructuring-bind (x y)
(ensure-coord coord)
(list x y)))
现在让我们将代码段放在一个函数中:
(defun coord-from-string (str)
(destructuring-bind (x y)
(ensure-coord
(read-from-string str))
(list x y)))
这对我来说非常好。但实际上......为什么我们又需要destructuring-bind
?现在它完全没用了:
(defun coord-from-string (str)
(ensure-coord
(read-from-string str))
奖励提示:defstruct
使用(:type list)
。
为了进一步明确和表达,您可以为对象声明一个defstruct。 E.g:
(defstruct (coord (:type list))
(x 0d0 :type double-float)
(y 0d0 :type double-float))
这样,两个元素的每个列表都可以使用Common Lisp提供的函数来操作结构。 E.g:
(coord-y '(1d0 2d0)) ; => 2.0d0
请注意声明中的(:type list)
。这就是我们可以使用简单列表作为结构的原因。这样我们就可以将列表的多功能性与结构的功能和表现力结合起来。例如,如果您按照此提示操作,ensure-coord
函数将如下所示:
(defun ensure-coord (coord)
(if (coord-p coord)
coord
(make-coord)))
可以说,意图更清晰。
答案 1 :(得分:3)
有几种方法可以解决这个问题。一种选择是使用模式匹配库(例如Trivia)。
(defun read-coord (string)
(match (read-from-string string nil)
((list x y) (list x y))
(_ (list 0d0 0d0))))
CL-USER> (read-coord "(1d0 2d0)")
(1.0d0 2.0d0)
CL-USER> (read-coord "(1d0)")
(0.0d0 0.0d0)
CL-USER> (read-coord "")
(0.0d0 0.0d0)
如果要检查X
和Y
是否为浮点数,可以添加保护模式
(defun read-coord (string)
(match (read-from-string string nil)
((list (guard x (floatp x))
(guard y (floatp y)))
(list x y))
(_ (list 0d0 0d0))))
正如Rainer Joswig在his answer中指出的那样,READ-FROM-STRING
可能会导致其他错误,*READ-EVAL*
在阅读时应设置为NIL
。
(defun safely-read-from-string (string)
(let ((*read-eval* nil))
(ignore-errors (read-from-string string))))
(defun read-coord (string)
(match (safely-read-from-string string)
((list (guard x (floatp x))
(guard y (floatp y)))
(list x y))
(_ (list 0d0 0d0))))
另请注意,您可以将&OPTIONAL
与lambda list关键字一起使用DESTRUCTURING-BIND
。使用Alexandria作为ENSURE-LIST
- 函数,您可以编写
(defun read-coord (string)
(destructuring-bind (&optional (x 0d0) (y 0d0) &rest _)
(ensure-list (safely-read-from-string string))
(declare (ignore _))
(list x y)))
CL-USER> (read-coord "(1d0 2d0)")
(1.0d0 2.0d0)
CL-USER> (read-coord "(1d0)")
(1.0d0 0.0d0)
CL-USER> (read-coord "")
(0.0d0 0.0d0)
如果您不想使用任何库,您可以检查是否自己给出了一个列表
(defun read-coord (string)
(let ((coord (safely-read-from-string string)))
(destructuring-bind (&optional (x 0d0) (y 0d0) &rest _)
(if (listp coord)
coord
(list coord))
(declare (ignore _))
(list x y))))
答案 2 :(得分:1)
请注意,有许多可能的错误来源。例如,读者可以检测错误。
确保您处理错误:
(defun get-it (s)
(let ((*read-eval* nil)) ; don't execute code on reading!
(flet ((check (it)
(if (and (listp it)
(= (length it) 2)
(every #'double-float-p it))
it
(error "data is not a list of two double-floats: ~a" it))))
(handler-case (check (read-from-string s))
(error (condition)
(princ condition)
(list 0.0d0 0.0d0))))))
CL-USER 34 > (get-it "(0.0d0 0.0d0)")
(0.0D0 0.0D0)
CL-USER 35 > (get-it "(0.0d0 0.0d0")
End of file while reading stream #<SYSTEM::STRING-INPUT-STREAM 40E06AD7DB>.
(0.0D0 0.0D0)
CL-USER 36 > (get-it "(0.0d0 foo:aa))")
Reader cannot find package FOO.
(0.0D0 0.0D0)
CL-USER 37 > (get-it ")")
Unmatched right parenthesis.
(0.0D0 0.0D0)
CL-USER 38 > (get-it "(1 2 3)")
data not a list of two double-floats: (1 2 3)
(0.0D0 0.0D0)