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
的想法类似,但是语法不是很清晰,但可以理解,因为它还必须处理结构和类以及
)。 描述了它可以处理的所有其他用例