我无法想象,是否有任何方法在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的模式匹配库,如果是的话-哪一个?
这在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可能还有更花哨的版本
语言中没有为此内置任何内容。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 list-directed Destructuring中描述的"Lambda-list-directed"方法,你可以使用Lambda-list关键字,如&optional, &key等,那么事情就复杂多了,因为你不应该替换其中某些部分的变量。例如,如果您有
&optional (_x '_default-x)
则可以将_x
替换为某些东西,但不是_default-x
,因为后者不是模式。但是,在Lisp中,代码就是数据,因此我们仍然可以编写一个宏,它映射到解构-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))