<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                ## 第 12 章 廣義變量 第 8 章曾提到,宏的長處之一是其變換參數的能力。`setf`?就是這類宏中的一員。本章將著重分析`setf`?的內涵,然后以幾個宏為例,它們將建立在?`setf`?的基礎之上。 要在?`setf`?上編寫正確無誤的宏并非易事,其難度讓人咋舌。為了介紹這個主題,第一節會先給出一個有點小問題的簡單例子。接下來的小節將解釋該宏的錯誤之處,然后展示如何改正它。第三和第四節會介紹一些基于?`setf`?的實用工具的例子,而最后一節則會說明如何定義你自己的?`setf`?逆變換。 ### 12.1 概念 內置宏?`setf`?是?`setq`?的推廣形式。`setf`?的第一個參數可以是個函數調用而非簡單的變量: ~~~ > (setq lst '(a b c)) (A B C) > (setf (car lst) 480) 480 > lst (480 B C) ~~~ 一般而言,`(setf x y)`?可以理解成 "務必讓 x 的求值結果為 y"。作為一個宏,`setf`?得以深入到參數內部,弄清需要做哪些工作,才能滿足這個要求。如果第一個參數(在宏展開以后) 是個符號,那么`setf`?就只會展開成 setq。但如果第一個參數是個查詢語句,那么?`setf`?則會展開到對應的斷言上。由于第二個參數是常量,所以前面的例子可以展開成: ~~~ (progn (rplaca lst 480) 480) ~~~ 這種從查詢到斷言的變換被稱為逆變換。Common Lisp 中所有最常用的訪問函數都有預定義的逆,包括?`car`、`cdr`、`nth`、`aref`、`get`、`gethash`,以及那些由?`defstruct`?創建的訪問函數。( 完整的名單見?**CLTL2**?的第 125 頁。) 能充當?`setf`?第一個參數的表達式被稱為廣義變量。廣義變量已經成為了一種強有力的抽象機制。宏調用和廣義變量的相似之處在于:一個宏調用,只要能展開成可逆引用,那么其本身就一定是可逆的。 當我們也加入這個行列,基于?`setf`?編寫自己的宏時,這種組合可以產生顯而易見更清爽的程序。我們可以在?`setf`?上面定義的宏有很多,其中一個是?`toggle`:【注1】 ~~~ (defmacro toggle (obj) '(setf ,obj (not ,obj))) ~~~ 它可以反轉一個廣義變量的值: ~~~ > (let ((lst '(a b c))) (toggle (car lst)) lst) (NIL B C) ~~~ 現在考慮下面的應用。假設有個人,他可能是個肥皂劇作者、精力充沛的好事者,或是居委會大媽,想要維護一個數據庫。其中記錄著小鎮上所有居民之間的種種恩怨情仇。在數據庫里的表里,其中有一張便是用來保存朋友關系的: ~~~ (defvar *friends* (make-hash-table)) ~~~ 這個哈希表的表項本身也是哈希表,其中,潛在的朋友被映射到?`t`?或者?`nil`?: ~~~ (setf (gethash 'mary *friends*) (make-hash-table)) ~~~ 為了使 John 成為 Mary 的朋友,我們可以說: ~~~ (setf (gethash 'john (gethash 'mary *friends*)) t) ~~~ 這個鎮被分為兩派。正如幫派的傳統,每個人都聲稱 "凡人非友即敵",所以鎮上所有人都被迫加入一方或者另一方。這樣當某人轉變立場時,他所有的朋友都變成敵人,而所有的敵人則變成朋友。 如果只用內置的操作符來切換?`x`?和?`y`?的敵友關系,我們必須這樣說: ~~~ (setf (gethash x (gethash y *friends*)) (not (gethash x (gethash y *friends*)))) ~~~ 盡管去掉?`setf`?后要簡單許多,這個表達式還是相當復雜。倘若我們為數據庫定義了一個訪問宏,如下: ~~~ (defmacro friend-of (p q) '(gethash ,p (gethash ,q *friends*))) ~~~ 那么在這個宏和?`toggle`?的協助下,我們就得以更方便地修改數據庫的數據。前面那個更新數據庫的語句可以簡化成: ~~~ (toggle (friend-of x y)) ~~~ 廣義變量就像是美味的健康食品。它們能讓你的程序良好地模塊化,同時變得更為優雅。如果你給出宏或者可逆函數,用來訪問你的數據結構,那么其他模塊就可以使用?`setf`?來修改你的數據結構而無需了解其內部細節。 ### 12.2 多重求值問題 上一節曾警告說,我們最初的?`toggle`?定義是不正確的: ~~~ (defmacro toggle (obj) ; wrong '(setf ,obj (not ,obj))) ~~~ 它會碰到第 10.1 節里提到的多重求值問題。如果它的參數有副作用,那麻煩就來了。比如說,若`lst`?是一個對象列表,我們這樣寫: ~~~ (toggle (nth (incf i) lst)) ~~~ 并期待它能反轉第?`(i+1)`?個元素。事與愿違,如果使用?`toggle`?現在的定義,這個調用將展開成: ~~~ (setf (nth (incf i) lst) (not (nth (incf i) lst))) ~~~ 這會使 i 遞增兩次,并且將第?`(i+1)`?個元素設置成第?`(i+2)`?個元素的反。所以在本例中: ~~~ > (let ((lst '(t nil t)) (i -1)) (toggle (nth (incf i) lst)) lst) (T NIL T) ~~~ 調用?`toggle`?毫無效果。 僅僅把作為?`toggle`?參數給出的表達式插入到?`setf`?的第一個參數的位置上還不夠。我們必須深入到表達式內部,看看它到底做了什么:如果它含有?`subform`?,而且這些?`subform`?有副作用的話,我們就需要把它們分開,并單獨求值。一般而言,這件事情并不那么簡單。 為了讓問題容易些,Common Lisp 提供了一個宏,它可以幫助我們自動定義一些基于?`setf`?的宏,不過適用范圍有限。宏的名字叫?`define-modify-macro`?,它接受三個參數:被定義宏的宏名,它的附加參數(出現在廣義變量之后),以及一個函數名,這個函數將為廣義變量產生新值。【注2】【注3】 使用?`define-modify-macro`?,我們可以像下面這樣定義?`toggle`?: ~~~ (define-modify-macro toggle () not) ~~~ 具體說,就是 "若要求值形如 (toggle place) 的表達式,應該先找到?`place`?指定的位置,并且,如果保存在那里的值是?`val`,將其替換成?`(not val)`?的值"。下面把這個新宏用在原來的例子里: ~~~ > (let ((lst '(t nil t)) (i -1)) (toggle (nth (incf i) lst)) lst) (NIL NIL T) ~~~ 雖然這個版本正確無誤地給出了結果,但它本可以寫得更通用些。由于?`setf`?和?`setq`?兩者對其參數數量都沒有限制,`toggle`?也應如此。我們可以通過在修改宏 (modify-macro) 的基礎上定義另一個宏,來賦予它這種能力,如 [示例代碼 12.1]所示。 * * * **[示例代碼 12.1]:操作在廣義變量上的宏** ~~~ (defmacro allf (val &rest args) (with-gensyms (gval) '(let ((,gval ,val)) (setf ,@(mapcan #'(lambda (a) (list a gval)) args))))) (defmacro nilf (&rest args) '(allf nil ,@args)) (defmacro tf (&rest args) '(allf t ,@args)) (defmacro toggle (&rest args) '(progn ,@(mapcar #'(lambda (a) '(toggle2 ,a)) args))) (define-modify-macro toggle2 () not) ~~~ * * * ### 12.3 新的實用工具 本節將給出一些新的實用工具為例,我們用它們對廣義變量進行操作。這些實用工具必須是宏,以便將參數原封不動地傳給?`setf`。 [示例代碼 12.1] 中有四個基于?`setf`?的新宏。第一個是?`allf`?,它被用來將同一值賦給多個廣義變量。`nilf`?和?`tf`?就是基于它實現的,它們分別將參數設置 為?`nil`?和?`t`?。雖然這些宏很簡單,但是方便實用。 和?`setq`?一樣,`setf`?也可以接受多個參數 -- 即交替出現的變量和對應的值: ~~~ (setf x 1 y 2) ~~~ 這些新的實用工具同樣有這個能力,而且只用傳原來一半的參數就可以了。如果你想要把多個變量初始化為?`nil`?,那么可以不再使用: ~~~ (setf x nil y nil z nil) ~~~ 而改成說: ~~~ (nilf x y z) ~~~ 就行了。最后一個宏是前一節曾介紹過的?`toggle`?:它和?`nilf`?差不多,但給每個參數設置的是真值的反。 這四個宏說明了關于賦值操作符的一個要點。就算我們只需要對普通變量使用一個操作符,而把這個操作符號展開成?`setf`?而非?`setq`?,這樣做,有百利而無一害。如果第一個參數是符號,`setf`?將直接展開到?`setq`。由于不費吹灰之力,就能擁有?`setf`?的一般性,所以很少有必要在展開式里使用`setq`。 * * * **[示例代碼 12.2] 廣義變量上的列表操作** ~~~ (define-modify-macro concf (obj) nconc) (defun conc1f/function (place obj) (nconc place (list obj))) (define-modify-macro conc1f (obj) conc1f/function) (defun concnew/function (place obj &rest args) (unless (apply #'member obj place args) (nconc place (list obj)))) (define-modify-macro concnew (obj &rest args) concnew/function) ~~~ * * * [示例代碼 12.2] 【注4】包含三個破壞性修改列表結尾的宏。第 3.1 節提到依賴 ~~~ (nconc x y) ~~~ 的副作用是不可靠的,并且必須改成:【注5】 ~~~ (setq x (nconc x y)) ~~~ 這一習慣用法被嵌入在?`concf`?中了。更特殊的?`conc1f`?和?`concnew`?就像是用于列表另一端的`push`?和?`pushnew`,`conc1f`?在列表結尾追加一個元素,而?`concnew`?的功能相同,但只有當這個元素不在列表中時才會動作。 第 2.2 節曾提到,函數的名字既可以是符號,也可以是–表達式。因此,把整個λ表達式作為第三個參數傳給?`define-modify-macro`?也是可行的,正如?`conc1f`?的定義。【注6】 如果用第 4.3 節上的`conc1`?的話,這個宏也可以寫成: ~~~ (define-modify-macro conc1f (obj) conc1) ~~~ 在一種情況下,[示例代碼 12.2] 中的宏應該限制使用。如果你正準備通過在結尾處追加元素的方式來構造列表,那么最好用?`push`?,最后再?`nreverse`?這個列表。在列表的開頭處理數據比在結尾要方便些,因為在結尾處處理數據的話,你首先得到那里。Common Lisp 有許多用于前者的操作符,而適用于后者的操作符則屈指可數,這很可能是為了鼓勵程序員設計更高效率的程序。 ### 12.4 更復雜的實用工具 并非所有基于 setf 的宏都可以用 define-modify-macro 定義。比如說,假設我們想要定義一個宏 _f ,讓它破壞性把函數應用于一個廣義變量。內置宏 incf 就相當于使用了 + 的 setf 的縮寫。把: ~~~ (setf x (+ x y)) ~~~ 取而代之,我們只需說: ~~~ (incf x y) ~~~ 新的宏?`_f`?就是上述思路的推廣:`incf`?能展開成對?`+`?的調用,而?`_f`?則會展開成對由第一個參數給出操作符的調用。例如,在第 8.3 節 scale-objs 的定義里,我們必須這樣寫: ~~~ (setf (obj-dx o) (* (obj-dx o) factor)) ~~~ 改用?`_f`?的話,將變成: ~~~ (_f * (obj-dx o) factor) ~~~ `_f`?可能會被錯寫成: ~~~ (defmacro _f (op place &rest args) ; wrong '(setf ,place (,op ,place ,@args))) ~~~ 不幸的是,我們無法用?`define-modify-macro`?正確無誤地定義?`_f`?,因為應用到廣義變量上的操作符是由參數給定的。 這類更復雜的宏必須由手工編寫。為了讓這種宏的編寫方便些,`Common Lisp`?提供了函數?`get-setf-expansion`?【注7】,它接受一個廣義變量并返回所有用于獲取和設置其值的必要信息。通過為下面表達式手工生成展開式,我們將了解如何使用這些信息: `(incf (aref a (incf i)))` 當我們對廣義變量調用?`get-setf-expansion`?時,可以得到五個值用作宏展開式的原材料: ~~~ > (get-setf-expansion '(aref a (incf i))) (#:G4 #:G5) (A (INCF I)) (#:G6) (SYSTEM:SET-AREF #:G6 #:G4 #:G5) (AREF #:G4 #:G5) ~~~ 最開始的兩個值分別是臨時變量列表,以及應該給它們賦的值。因此,我們可以這樣開始展開式: ~~~ (let* ((#:g4 a) (#:g5 (incf i))) ...) ~~~ 這些綁定應該在?`let*`?里創建。因為一般來說,這些值?`form`?可能會引用到前面的變量。第三【注8】和第五個值是另一個臨時變量和將返回廣義變量初值的?`form`。由于我們想要在這個值上加?`1`,所以把后者包在對?`1+`?的調用里: ~~~ (let* ((#:g4 a) (#:g5 (incf i)) (#:g6 (1+ (aref #:g4 #:g5)))) ...) ~~~ 最后,`get-setf-expansion`?返回的第四個值是一個賦值的表達式,該賦值必須在新綁定環境下進行: ~~~ (let* ((#:g4 a) (#:g5 (incf i)) (#:g6 (1+ (aref #:g4 #:g5)))) (system:set-aref #:g6 #:g4 #:g5)) ~~~ 不過,這個?`form`?多半會引用一些內部函數,而這些內部函數不屬于 Common Lisp 標準。通常`setf`?掩蓋了這些函數的存在,但它們必須存在于某處。因為關于它們的所有東西都依賴于具體的實現,所以注重可移植性的代碼應該使用由?`get-setf-expansion`?返回的這些?`form`,而不是直接引用諸如?`system:set-aref`?這樣的函數。 現在為實現?`_f`?而編寫的宏,所要完成的工作,幾乎和我們剛才手工展開?`incf`?時做的事情完全一樣。唯一的區別就是,不再把?`let*`?里的最后一個?`form`?包裝在?`1+`?調用里,而是將它包裝在來自`_f`?參數的一個表達式里。[示例代碼 12.3] 給出了?`_f`?的定義。 * * * **[示例代碼 12.3] setf 上更復雜的宏** ~~~ (defmacro _f (op place &rest args) (multiple-value-bind (vars forms var set access) (get-setf-expansion place) '(let* (,@(mapcar #'list vars forms) (,(car var) (,op ,access ,@args))) ,set))) (defmethod pull (obj place &rest args) (multiple-value-bind (vars forms var set access) (get-setf-expansion place) (let ((g (gensym))) '(let* ((,g ,obj) ,@(mapcar #'list vars forms) (,(car var) (delete ,g ,access ,@args))) ,set)))) (defmacro pull-if (test place &rest args) (multiple-value-bind (vars forms var set access) (get-setf-expansion place) (let ((g (gensym))) '(let* ((,g ,test) ,@(mapcar #'list vars forms) (,(car var) (delete-if ,g ,access ,@args))) ,set)))) (defmacro popn (n place) (multiple-value-bind (vars forms var set access) (get-setf-expansion place) (with-gensyms (gn glst) '(let* ((,gn ,n) ,@(mapcar #'list vars forms) (,glst ,access) (,(car var) (nthcdr ,gn ,glst))) (prog1 (subseq ,glst 0 ,gn) ,set))))) ~~~ * * * 這是個很有用的實用工具。舉個例子,現在在它的幫助下,我們就可以輕易地將任意有名函數替換成其記憶化(第5.3 節)的等價函數。【注9】要對?`foo`?進行記憶化的處理,可以用: ~~~ (_f memoize (symbol-function 'foo)) ~~~ 使用?`_f`?,也有助于簡化其他基于?`setf`?的宏的定義。例如,我們現在可以把?`conc1f`?([示例代碼 12.2])定義成: ~~~ (defmacro conc1f (lst obj) '(_f nconc ,lst (list ,obj))) ~~~ [示例代碼 12.3] 中還有其他一些有用的宏,它們同樣基于?`setf`。下一個是?`pull`?,它是內置的`pushnew`?的逆操作。 這對操作符,就像是給?`push`?和?`pop`?賦予了一定的鑒別能力。如果給定的新元素不是列表的成員,`pushnew`?就把它加入到這個列表里面,而?`pull`?則是破壞性地從列表里刪除給定的元素。`pull`?定義中的?`&rest`?參數使?`pull`?可以接受和?`delete`?相同的關鍵字參數: ~~~ > (setq x '(1 2 (a b) 3)) (1 2 (A B) 3) > (pull 2 x) (1 (A B) 3) > (pull '(a b) x :test #'equal) (1 3) > x (1 3) ~~~ 你幾乎可以把這個宏當成這樣定義的: ~~~ (defmacro pull (obj seq &rest args) ; wrong '(setf ,seq (delete ,obj ,seq ,@args))) ~~~ 不過,如果它真的這樣定義,它將同時碰到求值順序和求值次數方面的問題。我們也可以把?`pull`?定義成簡單的修改宏: ~~~ (define-modify-macro pull (obj &rest args) (lambda (seq obj &rest args) (apply #'delete obj seq args))) ~~~ 但由于修改宏必須將廣義變量作為第一個參數,所以我們只得以相反的次序給出前兩個參數,這樣顯得有些不自然。 更通用的?`pull-if`?接受一個初始的函數參數,并且會展開成?`delete-if`?而非?`delete`?: ~~~ > (let ((lst '(1 2 3 4 5 6))) (pull-if #'oddp lst) lst) (2 4 6) ~~~ 這兩個宏說明了另一個有普遍意義的要點。如果下層函數接受可選參數,建立在其上的宏也應該這樣做。 `pull`?和?`pull-if`?都把可選參數傳給了它們的?`delete`?。 [示例代碼 12.3] 中最后一個宏是?`popn`?,它是?`pop`?的推廣形式。其功能不再是僅僅從列表里彈出一個元素,而是能彈出并返回任意長度的子序列: ~~~ > (setq x '(a b c d e f)) (A B C D E F) > (popn 3 x) (A B C) > x (D E F) ~~~ [示例代碼 12.4] 中的宏能對它的參數排序。如果?`x`?和?`y`?是變量,而且我們想要確保x 的值不是兩個值中較小的那個,那么我們可以寫: ~~~ (if (> y x) (rotatef x y)) ~~~ 但如果我們想對三個或者數量更多的變量做這個操作,所需的代碼量就會迅速膨脹。與其手工編寫這樣的代碼,不妨讓?`sortf`?來為我們代勞。這個宏接受一個比較操作符,還有任意數量的廣義變量,然后不斷交換它們的值,直到這些廣義變量的順序符合操作符的要求。在最簡單的情形,參數可以是普通變量: * * * **[示例代碼 12.4] 一個排序其參數的宏** ~~~ (defmacro sortf (op &rest places) (let* ((meths (mapcar #'(lambda (p) (multiple-value-list (get-setf-expansion p))) places)) (temps (apply #'append (mapcar #'third meths)))) '(let* ,(mapcar #'list (mapcan #'(lambda (m) (append (first m) (third m))) meths) (mapcan #'(lambda (m) (append (second m) (list (fifth m)))) meths)) ,@(mapcon #'(lambda (rest) (mapcar #'(lambda (arg) '(unless (,op ,(car rest) ,arg) (rotated ,(car rest) ,arg))) (cdr rest))) temps) ,@(mapcar #'fourth meths)))) ~~~ * * * ~~~ > (setq x 1 y 2 z 3) 3 > (sortf > x y z) 3 > (list x y z) (3 2 1) ~~~ 一般情況下,它們可以是任何可逆的表達式。假設?`cake`?是一個可逆函數,它能返回某人的蛋糕,而`bigger`?是個針對蛋糕的比較函數。如果我們想要推行一個規定,要求?`moe`?的?`cake`?不得小于`larry`?的?`cake`?,而后者的?`cake`?也不得小于?`curly`?的,我們寫成: ~~~ (sortf bigger (cake 'moe) (cake 'larry) (cake 'curly)) ~~~ `sortf`?的定義的大致結構和?`_f`?差不多。它以一個?`let*`?開始,在這個?`let*`?表達式中,由?`get-setf-expansion`?返回的臨時變量被綁定到廣義變量的初始值上。`sortf`?的核心是中間的?`mapcon`表達式,該表達式生成的代碼將被用來對這些臨時變量進行排序。宏的這部分生成的代碼量會隨著參數個數以指數速度增長。在排序之后,廣義變量會被用那些由?`get-setf-expansion`?返回的?`form`重新賦值。這里使用的算法是 的冒泡排序,但如果調用的時候參數非常多的話,這個宏就不適用了。 [示例代碼 12.5] 給出的是對 sortf 調用的展開式。在最前面的 let* 中,參數和它們的 subform 按照從左到右的順序小心地求值。之后出現的三個表達式分別比較幾個臨時變量的值,有可能還會交換它們:先是比較第一個和第二個,接著是第一個和第三個,然后第二個和第三個。最后廣義變量從左到右被重新賦值。盡管很少需要注意這個問題,但還是提一下:通常,宏參數應該按從左到右的順序進行賦值,這和它們求值的順序是一致的。 有些操作符,如?`_f`?和?`sortf`?,它們與接受函數型參數的函數之間確實有相似之處。不過也應該認識到它們是完全不同的東西。類似?`find-if`?的函數接受一個函數并調用它;而類似?`_f`?的宏接受的則是一個名字,這些宏會讓它成為一個表達式的?`car`。讓?`_f`?和?`sortf`?都接受函數型參數也不無可能。例如,`_f`?可以這樣實現: ~~~ (sortf > x (aref ar (incf i)) (car lst)) ~~~ 展開(在某個可能的實現里) 成: * * * **[示例代碼 12.5] 一個 sortf 調用的展開式** ~~~ (let* ((#:g1 x) (#:g4 ar) (#:g3 (incf i)) (#:g2 (aref #:g4 #:g3)) (#:g6 lst) (#:g5 (car #:g6))) (unless (> #:g1 #:g2) (rotatef #:g1 #:g2)) (unless (> #:g1 #:g5) (rotatef #:g1 #:g5)) (unless (> #:g2 #:g5) (rotatef #:g2 #:g5)) (setq x #:g1) (system:set-aref #:g2 #:g4 #:g3) (system:set-car #:g6 #:g5)) ~~~ * * * ~~~ (defmacro _f (op place &rest args) (let ((g (gensym))) (multiple-value-bind (vars forms var set access) (get-setf-expansion place) '(let* ((,g ,op) ,@(mapcar #'list vars forms) (,(car var) (funcall ,g ,access ,@args))) ,set)))) ~~~ 然后調用?`(_f #'+ x 1)`。但是?`_f`?原來的版本不但擁有這個版本的所有功能,而且由于它處理的是名字,所以它還可以接受宏或者?`special form`?的名字。就像?`+`?那樣,比如說,你還可以調用`nif`?(102頁): ~~~ > (let ((x 2)) (_f nif x 'p 'z 'n) x) P ~~~ ### 12.5 定義逆 12.1 節說明了一個道理:如果一個宏調用能展開成可逆引用,那么它本身應該也是可逆的。不過,你也用不著只是為了可逆,就把操作符定義成宏。通過使用?`defsetf`?,你可以告訴?`Lisp`?如何對任意的函數或宏調用求逆。 使用這個宏的方法有兩種。在最簡單的情況下,它的參數是兩個符號: ~~~ (defsetf symbol-value set) ~~~ 如果用更復雜的方法,那么?`defsetf`?的調用和?`defmacro`?調用會有幾分相似,它另外帶有一個參數用于更新值?`form`。例如,下式可以為?`car`?定義一種可能的逆: ~~~ (defsetf car (lst) (new-car) '(progn (rplaca ,lst ,new-car) ,new-car)) ~~~ `defmacro`?和?`defsetf`?之間有一個重要的區別:后者會自動為其參數創建生成符號(gensym)。通過上面給出的定義,`(setf (car x) y)`?將展開成: ~~~ (let* ((#:g2 x) (#:g1 y)) (progn (rplaca #:g2 #:g1) #:g1)) ~~~ 這樣,我們寫?`defsetf`?展開器時就沒有后顧之憂,不用擔心諸如變量捕捉,或者求值的次數和順序之類的問題了。 在?**CLTL2**?的 Common Lisp 中,也可以直接用?`defun`?定義?`setf`?的逆。因而前面的示例也可以寫成: ~~~ (defun (setf car) (new-car lst) (rplaca lst new-car) new-car) ~~~ 新的值應該作為這個函數的第一個參數。同樣按照習慣,也應該把這個值作為函數的返回值。 目前為止的示例都認為,廣義變量應該指向數據結構中的某個位置。不法之徒把人質帶進地牢,而見義勇為之士則讓她重見天日;他們移動的路徑相同,但方向相反。所以,如果人們覺得?`setf`?的工作方式也只能是這樣,那不足為奇,因為所有預定義的逆看上去都是如此;確實,習慣上,將被求逆的參數也常會使用?`place`?作為其參數名。 從理論上說,`setf`?可以更一般化:`accessform`?和它的逆的操作對象甚至可以不是同種數據結構。假設在某個應用里,我們想要把數據庫的更新緩存起來。這可能是迫不得已的,舉例來說,倘若每次修改數據,都即時完成真正的更新操作,就有可能會降低效率,或者,如果要求所有的更新都必須在提交之前驗證一致性,那就必須引入緩存的機制。 * * * **[示例代碼 12.6] 一個非對稱的逆轉換** ~~~ (defvar *cache* (make-hash-table)) (defun retrieve (key) (multiple-value-bind (x y) (gethash key *cache*) (if y (values x y) (cdr (assoc key *world*))))) (defsetf retrieve (key) (val) '(setf (gethash ,key *cache*) ,val)) ~~~ * * * 假設?`\*world\*`?是實際的數據庫。為簡單起見,我們將它做成一個元素為?`(key . val)`?形式的關聯表(assoc-list)。[示例代碼 12.6] 顯示了一個稱為?`retrieve`?的查詢函數。如果?`\*world\*`?是: ~~~ ((a . 2) (b . 16) (c . 50) (d . 20) (f . 12)) ~~~ 那么: ~~~ > (retrieve 'c) 50 ~~~ 和?`car`?的調用不同,`retrieve`?調用并不指向一個數據結構中的特定位置。返回值可能來自兩個位置里的 一個。而?`retrieve`?的逆,同樣定義在 [示例代碼 12.6] 中,僅指向它們中的一個: ~~~ > (setf (retrieve 'n) 77) 77 > (retrieve 'n) 77 T ~~~ 該查詢返回第二個值?`t`?,以表明在緩存中找到了答案。 就像宏一樣,廣義變量是一種威力非凡的抽象機制。這里肯定還有更多的東西有待發掘。當然,有的用戶很可能已經發現了一些使用廣義變量的方法,使用這些方法能得到更優雅和強大的程序。但也不排除以全新的方式使用?`setf`?逆的可能性,或者發現其它類似的有用的變換技術。 備注: 【注1】這個定義是錯誤的,下一節將給出解釋。 【注2】一般意義上的函數名:`1+`?或者?`(lambda (x) (+ x 1))`?都可以。 【注3】譯者注:現行 Common Lisp 標準 (CLHS) 事實上要求?`define-modify-macro`?和?`define-compiler-macro`?的第三個參數的類型必須是符號。 【注4】譯者注:這里根據現行 Common Lisp 標準對源代碼加以修改,我們額外定義了兩個輔助函數以確保?`define-modify-macro`?的第三個參數只能是符號。 【注5】譯者注:當作為?`nconc`?第一個參數的變量為空列表,也就是?`nil`?時,該變量在?`nconc`?執行之后將仍是?`nil`?,而不是整個?`nconc`?表達式的那個相當于其第二個參數的值。 【注6】譯者注:正如前面兩個腳注里提到的那樣,Common Lisp 標準并沒有定義?`define-modify-macro`?的第三個參數可以是符號之外的其他東西,盡管λ表達式出現在一個函數調用形式的函數位置上確實是合法的。原書作者試圖通過類比來說明 λ表達式用在?`define-modify-macro`?中的合法性,這是不恰當的,請讀者注意。 【注7】譯者注:原書中給出的函數實際上是?`get-setf-method`?,但這個函數已經不在現行 Common Lisp 標準中了,參見?`X3J13 Issue 308`:`SETF-METHOD-VS-SETF-METHOD`?取代它的是`get-setf-expansion`?,這個函數接受兩個參數,`place`?以及可選的?`environment`?環境參數。本書后面對于所有采用?`get-setf-method`?的地方一律直接改用?`get-setf-expansion`?,不再另行說明。 【注8】第三個值當前總是一個單元素列表。它被返回成一個列表來提供(目前為止還不可能)在廣義變量中保存多值的可能性。 【注9】然而,內置函數是個例外,它們不應該以這種方式被記憶化。Common Lisp 禁止重定義內置函數。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看