如何避免解构绑定 ARG-COUNT-ERROR?

how to avoid destructuring-bind ARG-COUNT-ERROR?

我需要从数据库中读取一串 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 的格式不正确,代码段将 return 默认值。 : (0.0d0 0.0d0);

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

我对 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。例如:

(defstruct (coord (:type list))
   (x 0d0 :type double-float)
   (y 0d0 :type double-float))

这样,每个 list 的两个元素都可以使用 Common Lisp 提供的结构操作函数进行操作。例如:

(coord-y '(1d0 2d0)) ; => 2.0d0

注意声明中的(:type list)。这就是为什么我们可以使用一个简单的列表来充当结构体。这样我们就可以将列表的多功能性与结构的功能性和表现力结合起来。例如,如果您遵循此提示,ensure-coord 函数将如下所示:

(defun ensure-coord (coord)
  (if (coord-p coord)
      coord
      (make-coord)))

可以说,意图更明确。

有几种方法可以解决这个问题。一种选择是使用模式匹配库(例如 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 在 中指出的那样,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))))

另外请记住,您可以使用 lambda 列表关键字,例如 &OPTIONALDESTRUCTURING-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))))

请注意,可能的错误来源有很多。例如 reader 可以检测错误。

确保处理错误:

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