如何避免destruct-bind ARG-COUNT-ERROR?

时间:2016-12-15 04:40:11

标签: common-lisp sbcl

我需要从数据库中读取一串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);

我做得对吗? 有没有更好的方法来避免这个错误?

3 个答案:

答案 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)

如果要检查XY是否为浮点数,可以添加保护模式

(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)