我正在使用一个外部库中的函数,返回一个由四个数字组成的向量,并且我希望像使用destructuring-bind一样直接访问这些值。请看这个无意义的示例:
(defun a-vector ()
(vector 1 2 3 4))
(defun a-list ()
(list 1 2 3 4))
(destructuring-bind (a b c d)
(a-list)
(format t "~D ~D ~D ~D~%" a b c d))
(destructuring-bind (a b c d)
(coerce (a-vector) 'list)
(format t "~D ~D ~D ~D~%" a b c d))如果我将vector coerce到一个list中,这是可能的,因为性能在这里不是问题,所以它可能是好的。但我想知道有没有更简单的方法?
发布于 2020-05-19 03:07:08
可以将变量绑定到每个单元格,如下所示:
(defmacro with-aref ((&rest indices) array &body body)
(let ((a (gensym)))
`(let ((,a ,array))
(symbol-macrolet
,(loop
for n from 0
for i in indices
collect (list i `(aref ,a ,n)))
,@body))))您可以按如下方式使用它:
(with-aref (w x y z) vec
(setf w (+ x y z)))通过更多的工作,您还可以支持索引和不同类别的访问器。假设每个绑定都是一个三元组,其中i是标识符,n是表示数字索引的数字(或nil),k是:place、:value或nil;:place用symbol-macrolet绑定符号,:value只用< (i n k) >d11绑定它。
首先,让我们通过提供快捷方式来帮助用户:
x代表(x nil nil)(x o)或(x o nil)或(x nil o),具体取决于选项o是数字还是符号(在宏扩展时)。此外,我们可能希望自动忽略nil标识符、空符号||或以下划线开头的符号(例如_、_var)。
下面是标准化函数:
(defun normalize-index (index)
(flet ((ret (i n k)
(let ((ignored (or (null i)
(string= i "")
(char= #\_ (char (string i) 0)))))
(list (if ignored (gensym) i) n k ignored))))
(let ((index (alexandria:ensure-list index)))
(typecase index
(null (ret nil nil nil))
(cons (destructuring-bind (i &optional n (k nil kp)) index
(if kp
(ret i n k)
(etypecase n
(symbol (ret i nil n))
((integer 0) (ret i n nil))))))))))我们可以将此规范化应用于索引列表,并跟踪忽略的符号:
(defun normalize (indices)
(loop
for i in indices
for norm = (normalize-index i)
for (index number kind ignore) = norm
collect norm into normalized
when ignore
collect index into ignored
finally (return (values normalized ignored))))然后,我们处理规范化条目中的nil数。我们希望索引从上次使用的索引开始增加,或者由用户显式给出:
(defun renumber (indices)
(loop
for (v n k) in indices
for next = nil then (1+ index)
for index = (or n next 0)
collect (list v index k)))例如:
(renumber (normalize '(a b c)))
((A 0 NIL) (B 1 NIL) (C 2 NIL))
(renumber (normalize '((a 10) b c)))
((A 10 NIL) (B 11 NIL) (C 12 NIL))
(renumber (normalize '((a 10) (b 3) c)))
((A 10 NIL) (B 3 NIL) (C 4 NIL))我们对我们绑定的变量类型做同样的事情:
(defun rekind (indices)
(loop
for (v n k) in indices
for next = nil then kind
for kind = (or k next :place)
collect (list v n kind)))例如:
(rekind (normalize '(a b c)))
((A NIL :PLACE) (B NIL :PLACE) (C NIL :PLACE))
(rekind (normalize '(a (b :value) c)))
((A NIL :PLACE) (B NIL :VALUE) (C NIL :VALUE))最后,所有这些步骤在parse-indices中组合在一起
(defun parse-indices (indices)
(multiple-value-bind (normalized ignored) (normalize indices)
(values (rekind (renumber normalized))
ignored)))最后,宏如下所示:
(defmacro with-aref ((&rest indices) array &body body)
(multiple-value-bind (normalized ignored) (parse-indices indices)
(labels ((ignored (b) (remove-if-not #'ignoredp (mapcar #'car b)))
(ignoredp (s) (member s ignored)))
(loop
with a = (gensym)
for (i n k) in normalized
for binding = `(,i (aref ,a ,n))
when (eq k :value) collect binding into values
when (eq k :place) collect binding into places
finally (return
`(let ((,a ,array))
(let ,values
(declare (ignore ,@(ignored values)))
(symbol-macrolet ,places
(declare (ignore ,@(ignored places)))
,@body))))))))例如:
(let ((vec (vector 0 1 2 3 4 5 6 7 8 9 10)))
(prog1 vec
(with-aref ((a 2) (b :value) c _ _ d (e 0) (f 1)) vec
(setf a (list a b c d e f)))))上面的宏展开式如下:
(LET ((VEC (VECTOR 0 1 2 3 4 5 6 7 8 9 10)))
(LET ((#:G1898 VEC))
(LET ((#:G1901 VEC))
(LET ((B (AREF #:G1901 3))
(C (AREF #:G1901 4))
(#:G1899 (AREF #:G1901 5))
(#:G1900 (AREF #:G1901 6))
(D (AREF #:G1901 7))
(E (AREF #:G1901 0))
(F (AREF #:G1901 1)))
(DECLARE (IGNORE #:G1899 #:G1900))
(SYMBOL-MACROLET ((A (AREF #:G1901 2)))
(DECLARE (IGNORE))
(LET* ((#:G19011902 #:G1901)
(#:NEW1 (LIST (AREF #:G1901 2) B C D E F)))
(FUNCALL #'(SETF AREF) #:NEW1 #:G19011902 2)))))
#:G1898))它会产生以下结果
#(0 1 (2 3 4 7 0 1) 3 4 5 6 7 8 9 10)发布于 2020-05-19 18:12:19
coredump的回答很好。这是它的一个变体,它绑定变量而不是访问器,还允许您有选择地指定索引。所以
(with-vector-elements ((a 3) b) x
...)例如,将a绑定到(aref x 3)的结果,将b绑定到(aref x 4)的结果。
只有当你打算(a)不写回向量和(b)大量使用绑定,所以你想要避免很多可能的arefs (我不认为编译器在没有一些相当强的假设的情况下通常不能优化)时,这才是真正有用的。
(defmacro with-vector-elements ((&rest indices) vector &body forms)
(let ((canonical-indices
(loop with i = 0
for index in indices
collect (etypecase index
(symbol
(prog1
`(,index ,i)
(incf i)))
(cons
(destructuring-bind (var idx) index
(assert (and (symbolp var)
(typep idx '(and fixnum (integer 0))))
(var idx) "Invalid index spec")
(prog1
index
(setf i (1+ idx))))))))
(vname (gensym "V")))
`(let ((,vname ,vector))
(let ,(loop for (var index) in canonical-indices
collect `(,var (aref ,vname ,index)))
,@forms))))发布于 2020-05-22 16:00:15
还有一个名为metabang-bind的包--昵称为bind --其中的函数bind可以处理更多的解构情况:
(ql:quickload :metabang-bind)
(in-package :metabang-bind)
(bind ((#(a b c) #(1 2 3)))
(list a b c))
;; => (1 2 3)如果不使用in-package,您可以像bind:bind一样调用该函数。你可以将函数bind粗略地看作是一个destructuring-let* (类似于clojure的let,但是在语法上不是那么清晰,但是可以理解,因为它还必须处理结构和类以及values)。它可以处理的所有其他用例都描述为here。
https://stackoverflow.com/questions/61876099
复制相似问题