<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>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ## 第 17 章 讀取宏(read-macro) 在 Lisp 表達式的一生中,有三個最重要的時刻,分別是讀取期(read-time),編譯期(compile-time) 和運行期(runtime)。運行期由函數左右。宏給了我們在編譯期對程序做轉換的機會。本章討論讀取宏(read-macro),它們在讀取期發揮作用。 ### 17.1 宏字符 按照 Lisp 的一般哲學,你可以在很大程度上控制?`reader`?。它的行為是由那些可隨時改變的屬性和變量控制的。Reader 可以在幾個層面上編程。若要改變其行為,最簡單的方式就是定義新的宏字符。 宏字符(macro character) 是一種被 Lisp?`reader`?特殊對待的字符。舉個例子,小寫字母?`a`?的處理方式和小寫字母?`b`?是一樣的,它們都由常規的處理方式處理。但左括號就有些不同:它告訴 Lisp 開始讀取一個列表。 每個這樣的字符都有一個與之關聯的函數,告訴 Lisp?`reader`?當遇到該字符的時候做什么。你可以改變一個已有的宏字符的關聯函數,或者定義你自己的新的宏字符。 內置函數?`set-macro-character`?提供了一種定義讀取宏的方式。它接受一個字符和一個函數,以后當?`read`?遇到這個字符時,它就返回調用該函數的結果。 * * * **[示例代碼 17.1] '(引號)的可能定義** ~~~ (set-macro-character #\' #'(lambda (stream char) (declare (ignore char)) (list 'quote (read stream t nil t)))) ~~~ * * * Lisp 中最古老的讀取宏之一是單引號?`'`?,即引用。你也可以不用?`'`,而總是將?`'a`?寫成?`(quote a)`,但這將會非常煩人, 而且會降低代碼的可讀性。引用讀取宏使?`(quote a)`?可以簡寫成?`'a`。我們可以用 [示例代碼 17.1] 中的方法實現它。當?`read`?在一個普通的上下文中(例如,不在?`"a'b"`或?`|a'b|`?中) 遇到?`'`?時,它將返回在當前流和字符上調用這個函數的結果。(該函數忽略了它的第二個形參,因為它總是那個引用字符。) 所以當?`read`?看到?`'a`?時,它將返回?`(quote a)`。 `read`?的最后三個參數分別控制:是否在碰到?`end-of-file`?時報錯,如果不報錯的話返回什么值,以及這個?`read`?調用是否是發生在?`read`?調用中的(譯者注:關于?`read`?的最后一個參數(recursive-p),詳見?**CLTL**?中對?`read`?的解釋。) 。在幾乎所有的讀取宏里,第二和第四個參數都應該是?`t`?,所以第三個參數也就無關緊要了。 讀取宏和常規宏一樣,其實質都是函數。和生成宏展開的函數一樣,和宏字符相關的函數,除了作用于它讀取的流以外,不應該再有其他副作用。Common Lisp 明確聲明:一個與宏字符相關聯的函數何時被執行,或者被執行幾次 Common Lisp 對其將不給予保證。(見?**CLTL2**?的 543 頁。) 宏和讀取宏在不同的階段分析和觀察你的程序。宏在程序中發生作用時,它已經被 reader 解析成了 Lisp 對象,而讀取宏在程序還是文本的階段時,就對它施加影響了。盡管如此,通過在這些文本上調用 read ,一個讀取宏,如果它愿意的話,同樣可以得到解析后的 Lisp 對象。這樣說來,讀取宏至少和常規宏一樣強有力。 事實上,讀取宏至少在兩方面比常規宏更為強大。讀取宏可以影響 Lisp 讀取的每一樣東西,而宏只是在代碼里被展開。并且,由于讀取宏通常遞歸地調用 read,一個類似: ~~~ ''a ~~~ 的表達式將變成: ~~~ (quote (quote a)) ~~~ 而如果我們試圖用一個普通的宏來為?`quote`?定義縮略語的話: ~~~ (defmacro q (obj) '(quote ,obj)) ~~~ 它在某些情況下可以正常工作: ~~~ > (eq 'a (q a)) T ~~~ 但在被嵌套使用時就不行了。例如: ~~~ (q (q a)) ~~~ 將展開成: ~~~ (quote (q a)) ~~~ 譯者注:解決這個問題的正確方法是定義一個編譯器宏(compiler-macro)。Common Lisp 內置的?`define-compiler-macro`?用于定義編譯器宏,詳見?**CLTL**??? 中關于此操作符的說明。 ### 17.2?`dispatching`?宏字符 `#'`?和其他?`#`?開頭的讀取宏一樣,是一種稱為?`dispatching`?讀取宏的實例。這些讀取宏以兩個字符出現,其中第一個字符稱為?`dispatch`?字符。這類宏的目的,簡單說就是盡可能地充分利用?? ?? 字符集;如果只有單字符讀取宏的話,那么讀取宏的數量就會受限于字符集的大小。 你可以(通過使用?`make-dispatch-macro-character`) 來定義你自己的?`dispatching`?宏字符,但由于?`#`?已經定義了,所以你也可以直接用它。一些?`#`?打頭的組合就是特意為你保留的;其他的那些,如果 Common Lisp 還沒有給它們賦予含義的話,也可以拿來用。完整的列表可見?**CLTL2**?的第 531 頁。 * * * **[示例代碼17.2] 一個用于常數函數的讀取宏** ~~~ (set-dispatch-macro-character #\# #\? #'(lambda (stream char1 char2) (declare (ignore char1 char2)) '#'(lambda (&rest ,(gensym)) ,(read stream t nil t)))) ~~~ * * * 新的?`dispatching`?宏字符組合可以通過調用?`set-dispatch-macro-character`?函數定義,除了接受兩個字符參數以外和?`set-macro-character`?的用法差不多。一個預留給程序員的組合是?`#?`?。[示例代碼 17.2] 顯示了如何將這個組合定義成一個用于常數函數的讀取宏。現在?`#?2`?將被讀取為一個函數,其接受任意數量的參數,并且返回?`2`。例如: ~~~ > (mapcar #?2 '(a b c)) (2 2 2) ~~~ 這個例子里定義的新操作符看起來相當無聊,但在使用了很多函數型參數的程序里,常常會用到常數函數。 事實上,有些方言提供了一個名叫?`always`?的內置函數,專門用來定義它們。 注意到在這個宏字符的定義中使用宏字符是完全沒有問題的:和任何 Lisp 表達式一樣,當這個定義被讀取以后這些宏字符就都消失了。在?`#?`?的后面使用宏字符也是可以的。因為?`#?`?的定義調用了`read`?,所以諸如?`'`?和?`#'`?此類宏字符也可以正常使用: ~~~ > (eq (funcall #?'a) 'a) T > (eq (funcall #?#'oddp) (symbol-function 'oddp)) T ~~~ ### 17.3 定界符 * * * **[示例代碼 17.3] 一個定義定界符的讀取宏** ~~~ (set-macro-character #\] (get-macro-character #\))) (set-dispatch-macro-character #\# #\[ #'(lambda (stream char1 char2) (declare (ignore char1 char2)) (let ((accum nil) (pair (read-delimited-list #\] stream t))) (do ((i (ceiling (car pair)) (1+ i))) ((> i (floor (cadr pair))) (list 'quote (nreverse accum))) (push i accum))))) ~~~ * * * 除了簡單的宏字符,定義得最多的宏字符要算列表定界符了。另一個為用戶預留的組合字符是?`#[`?。[示例代碼 17.3] 給出的例子,顯示了把這個字符定義成一個更復雜的左括號的方法。它定義形如?`#[x y]`?的表達式,使得這樣的表達式被讀取為在?`x`?到?`y`?的閉區間上所有整數的列表: ~~~ > #[2 7] (2 3 4 5 6 7) ~~~ 這個讀取宏里,唯一的新東西是對?`read-delimited-list`?的調用,這個函數是一個完全為這種情況度身定制的內置函數。它的第一個參數是那個被當作列表結尾的字符。有其名才能行其實,為了把`]`?識別成定界符,程序在開始的地方調用了?`set-macro-character`。 * * * **[示例代碼17.4] 一個用于定義定界符讀取宏的宏** ~~~ (defmacro defdelim (left right parms &body body) '(ddfn ,left ,right #'(lambda ,parms ,@body))) (let ((rpar (get-macro-character #\)))) (defun ddfn (left right fn) (set-macro-character right rpar) (set-dispatch-macro-character #\# left #'(lambda (stream char1 char2) (declare (ignore char1 char2)) (apply fn (read-delimited-list right stream t)))))) ~~~ * * * 多數潛在的定界符讀取宏都將在很大程度上重復 [示例代碼 17.3] 中的代碼。或許可以寫個宏,讓它從這些機制中提煉出更抽象的接口,以簡化代碼。[ 示例代碼 17.4] 就是一個實現,我們可以像它那樣定義一個實用工具,用其定義定界符讀取宏。宏?`defdelim`?接受兩個字符,一個參數列表,以及一個代碼主體。參數列表和代碼主體隱式地定義了一個函數。一個對 defdelim 的調用將首個字符定義為?`dispatching`?讀取宏,它讀取到第二個字符為止,然后將這個函數應用到它讀到的東西,并返回其結果。 無獨有偶,[示例代碼 17.3] 中的函數體也迫切需要一個實用工具,事實上,這個實用工具已經定義過了:見 4.5 節的?`mapa-b`?。使用?`defdelim`?和?`mapa-b`?,[示例代碼 17.3] 中定義的讀取宏現在只需寫成: ~~~ (defdelim #\[ #\] (x y) (list 'quote (mapa-b #'identity (ceiling x) (floor y)))) ~~~ 定界符讀取宏也可以用來做函數復合。第5.4 節定義了一個用于函數復合的操作符: ~~~ > (let ((f1 (compose #'list #'1+)) (f2 #'(lambda (x) (list (1+ x))))) (equal (funcall f1 7) (funcall f2 7))) T ~~~ 當我們復合像?`list`?和?`1+`?這樣的內置函數時,沒有理由等到運行期才去對 compose 的調用求值。第 5.7 節建議一個替代方案;通過給一個?`compose`?表達式前綴?`sharp-dot`?讀取宏: ~~~ #.(compose #'list #'1+) ~~~ 我們可以令其在讀取期就被求值。 * * * **[示例代碼 17.5]:一個用于函數型復合的讀取宏** ~~~ (defdelim #\{ #\} (&rest args) '(fn (compose ,@args))) ~~~ * * * 這里我們給出一個與之類似但更清晰的解決方案。[示例代碼 17.5] 中定義的讀取宏定義了一個?`#{ }`形式的表達式,這個表達式將被讀取成 的復合。這樣: ~~~ > (funcall #{list 1+} 7) (8) ~~~ 它生成一個對?`fn`?(15.1 節) 的調用,該調用在編譯期創建函數。 ### 17.4 這些發生于何時 最后,澄清一個可能造成困惑的問題應該會有所幫助。如果讀取宏是在常規宏之前作用的話,那么宏是怎樣展開成含有讀取宏的表達式的呢?例如,這個宏: ~~~ (defmacro quotable () '(list 'able)) ~~~ 會生成一個帶有引用的展開式。還是說它沒有生成?事實上,真相是:這個宏定義中的兩個引用在這個?`defmacro`?表達式被讀取時,就都被展開了,展開結果如下 ~~~ (defmacro quotable () (quote (list (quote able)))) ~~~ 通常,在宏展開式里包含讀取宏是沒有什么問題的。因為一個讀取宏的定義在讀取期和編譯期之間將不會(或者說不應該) 發生變化。
                  <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>

                              哎呀哎呀视频在线观看