10

我想不通,有没有办法在 erlang 中放入类似 _ 的东西,用于解构绑定中的“未使用值”?

例如,我们有类似的东西:

(destructuring-bind ((_SNIPPET
                               (_TITLE . title)
                               (_DESCRIPTION . description)
                               _RESOURCE-ID (_VIDEO-ID . video-id)))) entry
                (declare (ignore 
                          _SNIPPET _TITLE _DESCRIPTION _RESOURCE-ID _VIDEO-ID))
                (list video-id title description)))

最好不要为每个未使用的值放置特定变量,并编写如下内容:

 (destructuring-bind ((_
                         (_ . title)
                         (_ . description)
                         (_ (_ . video-id)))) entry
                    (list video-id title description)))

有没有办法使用标准解构绑定或任何其他标准宏来获得这种行为?或者我必须使用一些类似 ML 的模式匹配库,如果是这样 - 哪个?

4

2 回答 2

10

这是不可能的DESTRUCTURING-BIND(你不能多次使用一个变量,一些编译器会抱怨)。您可以枚举变量 , _1, _2... 但是您必须忽略它们中的每一个。

LOOP可以做到:

CL-USER 23 > (loop for ((a b nil c) nil d) in '(((1 2 3 4) 5 6)
                                                ((1 2 3 4) 5 6))
                   collect (list a b c d))
((1 2 4 6) (1 2 4 6))

NIL用作通配符变量。

您可以重用LOOP宏:

(defmacro match-bind (pattern object &body body)
  `(loop with ,pattern = ,object
         while nil
         finally (return (progn ,@body))))

CL-USER 37 > (match-bind ((a b nil c) nil d)
                 '((1 2 3 4) 5 6)
               (list a b c d))
(1 2 4 6)

您可以使用某些LET-MATCH库中的一些。例如:https ://github.com/schani/clickr/blob/master/let-match.lisp 可能还有更多花哨的版本。

于 2014-08-23T17:45:41.200 回答
7

语言中没有为此内置任何内容。 Rainer Joswig 的回答指出循环可以做一些解构,但它做的几乎没有。在这个答案的早期版本中,我建议遍历解构 lambda 列表并收集以 _ 开头的所有符号的列表,并向表单添加声明以忽略这些变量。一个更安全的版本用一个新的变量替换每个变量(这样就没有重复的变量),并忽略它们。因此像

(destructuring-bind (_a (_b c)) object
  c)

将扩展为

(destructuring-bind (#:g1 (#:g2 c)) object
  (declare (ignore #:g1 #:g2))
  c)

如果您只使用3.4.4.1.1 Lambda 列表中的数据导向解构中描述的“数据导向”,这种方法将可以正常工作。但是,如果您使用3.4.4.1.2 Lambda Lists 的 Lambda-list-directed Destructuring 中描述的“lambda-list-directed”方法,您可以在其中使用 lambda-list 关键字,如 &optional、&key 等,那么事情复杂得多,因为您不应该替换其中某些部分的变量。例如,如果你有

&optional (_x '_default-x)

那么_x用某些东西替换可能是可以的,但不是_default-x,因为后者不是模式。但是,在 Lisp 中,代码就是数据,所以我们仍然可以编写一个宏来映射 destructuring-lambda-list 并仅在作为模式的位置进行替换。这里有一些毛茸茸的代码就是这样做的。这需要一个函数和一个解构 lambda 列表,并为 lambda 列表中的每个模式变量以及参数的类型(整体、必需、可选等)调用该函数。

(defun map-dll (fn list)
  (let ((result '())
        (orig list)
        (keywords '(&allow-other-keys &aux &body
                    &key &optional &rest &whole)))
    (labels ((save (x)
               (push x result))
             (handle (type parameter)
               (etypecase parameter
                 (list (map-dll fn parameter))
                 (symbol (funcall fn type parameter)))))
      (macrolet ((parse-keyword ((&rest symbols) &body body)
                   `(progn
                      (when (and (not (atom list))
                                 (member (first list) ',symbols))
                        (save (pop list))
                        ,@body)))
                 (doparameters ((var) &body body)
                   `(do () ((or (atom list) (member (first list) keywords)))
                      (save (let ((,var (pop list)))
                              ,@body)))))
        (parse-keyword (&whole)
          (save (handle :whole (pop list))))
        (doparameters (required)
         (handle :required required))
        (parse-keyword (&optional)
         (doparameters (opt)
          (if (symbolp opt)
              (handle :optional opt)
              (list* (handle :optional (first opt)) (rest opt)))))
        (when (and (atom list) (not (null list))) ; turn (... . REST) 
          (setq list (list '&rest list)))         ; into (... &rest REST)
        (parse-keyword (&rest &body)
         (save (handle :rest (pop list))))
        (parse-keyword (&key)
         (doparameters (key)
          (if (symbolp key)
              (handle :key key)
              (destructuring-bind (keyspec . more) key
                (if (symbolp keyspec)
                    (list* (handle :key keyspec) more)
                    (destructuring-bind (keyword var) keyspec
                      (list* (list keyword (handle :key var)) more)))))))
        (parse-keyword (&allow-other-keys))
        (parse-keyword (&aux)
         (doparameters (aux) aux))
        (unless (null list)
          (error "Bad destructuring lambda list: ~A." orig))
        (nreverse result)))))

使用它,很容易编写一个解构绑定*,它将每个以 _ 开头的模式变量替换为一个将在主体中被忽略的新变量。

(defmacro destructuring-bind* (lambda-list object &body body)
  (let* ((ignores '())
         (lambda-list (map-dll (lambda (type var)
                                 (declare (ignore type))
                                 (if (and (> (length (symbol-name var)) 0)
                                          (char= #\_ (char (symbol-name var) 0)))
                                     (let ((var (gensym)))
                                       (push var ignores)
                                       var)
                                     var))
                               lambda-list)))
    `(destructuring-bind ,lambda-list ,object
       (declare (ignore ,@(nreverse ignores)))
       ,@body)))

现在我们应该看看它产生的扩展:

(macroexpand-1
 '(destructuring-bind* (&whole (a _ . b)
                        c _ d
                        &optional e (f '_f)
                        &key g _h
                        &aux (_i '_j))
   object
   (list a b c d e f g)))
;=>                            
(DESTRUCTURING-BIND
    (&WHOLE (A #:G1041 &REST B) C #:G1042 D
     &OPTIONAL E (F '_F)
     &KEY G #:G1043
     &AUX (_I '_J))
    OBJECT
  (DECLARE (IGNORE #:G1041 #:G1042 #:G1043))
  (LIST A B C D E F G))

我们没有替换任何不应该替换的地方(初始化表单、辅助变量等),但我们已经处理了应该替换的地方。我们也可以在您的示例中看到这项工作:

(macroexpand-1
 '(destructuring-bind* ((_ (_ . title)
                         (_ . description)
                         _
                         (_ . video-id)))
   entry
   (list video-id title description)))
;=>
(DESTRUCTURING-BIND ((#:G1044 (#:G1045 &REST TITLE)
                              (#:G1046 &REST DESCRIPTION)
                              #:G1047
                              (#:G1048 &REST VIDEO-ID)))
    ENTRY
  (DECLARE (IGNORE #:G1044 #:G1045 #:G1046 #:G1047 #:G1048))
  (LIST VIDEO-ID TITLE DESCRIPTION))
于 2014-08-23T17:48:12.410 回答