Vector 向量的分解
我正在使用一个来自外部库的函数,该函数返回一个由四个数字组成的向量,我希望直接访问这些值,就像使用Vector 向量的分解,vector,common-lisp,destructuring,Vector,Common Lisp,Destructuring,我正在使用一个来自外部库的函数,该函数返回一个由四个数字组成的向量,我希望直接访问这些值,就像使用解构绑定一样。看看这个毫无意义的例子: (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
解构绑定
一样。看看这个毫无意义的例子:
(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))
如果我将
向量
强制到列表
中,这是可能的,因为性能在这里不是问题,这可能没问题。但我想知道是否有更简单的方法?您可以按如下方式将变量绑定到每个单元格:
(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)))
(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))))))))
您将按如下方式使用它:
(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)))
(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))))))))
再多做一点工作,您还可以支持索引和不同类别的访问器。假设每个绑定是一个三元组
(ink)
,其中i
是一个标识符,n
是一个表示数字索引的数字(或nil),而k
是:place
,:value
或nil:place
将符号与符号宏let
绑定,值
仅与let
绑定
首先,让我们通过提供快捷符号来帮助用户:
代表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))
(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))
(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)))))
对于绑定的变量类型,我们也会这样做:
(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)))
例如:
(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))
(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))
(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)))))
最后,所有这些步骤组合在解析索引中:
(defun parse-indices (indices)
(multiple-value-bind (normalized ignored) (normalize indices)
(values (rekind (renumber normalized))
ignored)))
最后,宏如下所示:
(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)))
(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))))))))
例如:
(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))
(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))
(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)
coredump的回答很可爱。这是它的一个变体,它绑定变量而不是访问器,还允许您选择指定索引。所以
(with-vector-elements ((a 3) b) x
...)
例如,将a
绑定到(aref x 3)
的结果,将b
绑定到(aref x 4)
的结果
如果您打算(a)不回写向量,并且(b)大量使用绑定,那么这只在coredump的答案上有用,因此您希望避免大量可能的aref
s(我认为编译器通常无法在没有一些相当强的假设的情况下进行优化)
还有一个名为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)
如果未在包中使用,则可以将函数调用为bind:bind
。
函数bind
您可以大致认为是一个解构let*
(与clojure的let
的想法类似,但是语法不是很清晰,但可以理解,因为它还必须处理结构和类以及值
)。
描述了它可以处理的所有其他用例