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

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                2.2 節介紹過 Common Lisp 的求值規則,現在你應該很熟悉了。本章的操作符都有一個共同點,就是它們都違反了求值規則。這些操作符讓你決定在程序當中何時要求值。如果普通的函數調用是 Lisp 程序的樹葉的話,那這些操作符就是連結樹葉的樹枝。 [TOC] ## 5.1 區塊 (Blocks)[](http://acl.readthedocs.org/en/latest/zhCN/ch5-cn.html#blocks "Permalink to this headline") Common Lisp 有三個構造區塊(block)的基本操作符:?`progn`?、?`block`?以及?`tagbody`?。我們已經看過?`progn`?了。在?`progn`?主體中的表達式會依序求值,并返回最后一個表達式的值: ~~~ > (progn (format t "a") (format t "b") (+ 11 12)) ab 23 ~~~ 由于只返回最后一個表達式的值,代表著使用?`progn`?(或任何區塊)涵蓋了副作用。 一個?`block`?像是帶有名字及緊急出口的?`progn`?。第一個實參應為符號。這成為了區塊的名字。在主體中的任何地方,可以停止求值,并通過使用?`return-from`?指定區塊的名字,來立即返回數值: ~~~ > (block head (format t "Here we go.") (return-from head 'idea) (format t "We'll never see this.")) Here we go. IDEA ~~~ 調用?`return-from`?允許你的程序,從代碼的任何地方,突然但優雅地退出。第二個傳給?`return-from`?的實參,用來作為以第一個實參為名的區塊的返回值。在?`return-from`?之后的表達式不會被求值。 也有一個?`return`?宏,它把傳入的參數當做封閉區塊?`nil`?的返回值: ~~~ > (block nil (return 27)) 27 ~~~ 許多接受一個表達式主體的 Common Lisp 操作符,皆隱含在一個叫做?`nil`?的區塊里。比如,所有由?`do`?構造的迭代函數: ~~~ > (dolist (x '(a b c d e)) (format t "~A " x) (if (eql x 'c) (return 'done))) A B C DONE ~~~ 使用?`defun`?定義的函數主體,都隱含在一個與函數同名的區塊,所以你可以: ~~~ (defun foo () (return-from foo 27)) ~~~ 在一個顯式或隱式的?`block`?外,不論是?`return-from`?或?`return`?都不會工作。 使用?`return-from`?,我們可以寫出一個更好的?`read-integer`?版本: ~~~ (defun read-integer (str) (let ((accum 0)) (dotimes (pos (length str)) (let ((i (digit-char-p (char str pos)))) (if i (setf accum (+ (* accum 10) i)) (return-from read-integer nil)))) accum)) ~~~ 68 頁的版本在構造整數之前,需檢查所有的字符。現在兩個步驟可以結合,因為如果遇到非數字的字符時,我們可以舍棄計算結果。出現在主體的原子(atom)被解讀為標簽(labels);把這樣的標簽傳給?`go`?,會把控制權交給標簽后的表達式。以下是一個非常丑的程序片段,用來印出一至十的數字: ~~~ > (tagbody (setf x 0) top (setf x (+ x 1)) (format t "~A " x) (if (< x 10) (go top))) 1 2 3 4 5 6 7 8 9 10 NIL ~~~ 這個操作符主要用來實現其它的操作符,不是一般會用到的操作符。大多數迭代操作符都隱含在一個?`tagbody`?,所以是可能可以在主體里(雖然很少想要)使用標簽及?`go`?。 如何決定要使用哪一種區塊建構子呢(block construct)?幾乎任何時候,你會使用?`progn`?。如果你想要突然退出的話,使用`block`?來取代。多數程序員永遠不會顯式地使用?`tagbody`?。 ## 5.2 語境 (Context)[](http://acl.readthedocs.org/en/latest/zhCN/ch5-cn.html#context "Permalink to this headline") 另一個我們用來區分表達式的操作符是?`let`?。它接受一個代碼主體,但允許我們在主體內設置新變量: ~~~ > (let ((x 7) (y 2)) (format t "Number") (+ x y)) Number 9 ~~~ 一個像是?`let`?的操作符,創造出一個新的詞法語境(lexical context)。在這個語境里有兩個新變量,然而在外部語境的變量也因此變得不可視了。 概念上說,一個?`let`?表達式等同于函數調用。在 2.14 節證明過,函數可以用名字來引用,也可以通過使用一個 lambda 表達式從字面上來引用。由于 lambda 表達式是函數的名字,我們可以像使用函數名那樣,把 lambda 表達式作為函數調用的第一個實參: ~~~ > ((lambda (x) (+ x 1)) 3) 4 ~~~ 前述的?`let`?表達式,實際上等同于: ~~~ ((lambda (x y) (format t "Number") (+ x y)) 7 2) ~~~ 如果有關于?`let`?的任何問題,應該是如何把責任交給?`lambda`?,因為進入一個?`let`?等同于執行一個函數調用。 這個模型清楚的告訴我們,由?`let`?創造的變量的值,不能依賴其它由同一個?`let`?所創造的變量。舉例來說,如果我們試著: ~~~ (let ((x 2) (y (+ x 1))) (+ x y)) ~~~ 在?`(+?x?1)`?中的?`x`?不是前一行所設置的值,因為整個表達式等同于: ~~~ ((lambda (x y) (+ x y)) 2 (+ x 1)) ~~~ 這里明顯看到?`(+?x?1)`?作為實參傳給函數,不能引用函數內的形參?`x`?。 所以如果你真的想要新變量的值,依賴同一個表達式所設立的另一個變量?在這個情況下,使用一個變形版本?`let*`?: ~~~ > (let* ((x 1) (y (+ x 1))) (+ x y)) 3 ~~~ 一個?`let*`?功能上等同于一系列嵌套的?`let`?。這個特別的例子等同于: ~~~ (let ((x 1)) (let ((y (+ x 1))) (+ x y))) ~~~ `let`?與?`let*`?將變量初始值都設為?`nil`?。`nil`?為初始值的變量,不需要依附在列表內: ~~~ > (let (x y) (list x y)) (NIL NIL) ~~~ `destructuring-bind`?宏是通用化的?`let`?。其接受單一變量,一個模式 (pattern) ── 一個或多個變量所構成的樹 ── 并將它們與某個實際的樹所對應的部份做綁定。舉例來說: ~~~ > (destructuring-bind (w (x y) . z) '(a (b c) d e) (list w x y z)) (A B C (D E)) ~~~ 若給定的樹(第二個實參)沒有與模式匹配(第一個參數)時,會產生錯誤。 ## 5.3 條件 (Conditionals)[](http://acl.readthedocs.org/en/latest/zhCN/ch5-cn.html#conditionals "Permalink to this headline") 最簡單的條件式是?`if`?;其余的條件式都是基于?`if`?所構造的。第二簡單的條件式是?`when`?,它接受一個測試表達式(test expression)與一個代碼主體。若測試表達式求值返回真時,則對主體求值。所以 ~~~ (when (oddp that) (format t "Hmm, that's odd.") (+ that 1)) ~~~ 等同于 ~~~ (if (oddp that) (progn (format t "Hmm, that's odd.") (+ that 1))) ~~~ `when`?的相反是?`unless`?;它接受相同的實參,但僅在測試表達式返回假時,才對主體求值。 所有條件式的母體 (從正反兩面看) 是?`cond`?,?`cond`?有兩個新的優點:允許多個條件判斷,與每個條件相關的代碼隱含在?`progn`?里。`cond`?預期在我們需要使用嵌套?`if`?的情況下使用。 舉例來說,這個偽 member 函數 ~~~ (defun our-member (obj lst) (if (atom lst) nil (if (eql (car lst) obj) lst (our-member obj (cdr lst))))) ~~~ 也可以定義成: ~~~ (defun our-member (obj lst) (cond ((atom lst) nil) ((eql (car lst) obj) lst) (t (our-member obj (cdr lst))))) ~~~ 事實上,Common Lisp 實現大概會把?`cond`?翻譯成?`if`?的形式。 總得來說呢,?`cond`?接受零個或多個實參。每一個實參必須是一個具有條件式,伴隨著零個或多個表達式的列表。當?`cond`?表達式被求值時,測試條件式依序求值,直到某個測試條件式返回真才停止。當返回真時,與其相關聯的表達式會被依序求值,而最后一個返回的數值,會作為?`cond`?的返回值。如果符合的條件式之后沒有表達式的話: ~~~ > (cond (99)) 99 ~~~ 則會返回條件式的值。 由于?`cond`?子句的?`t`?條件永遠成立,通常我們把它放在最后,作為缺省的條件式。如果沒有子句符合時,則?`cond`?返回?`nil`?,但利用`nil`?作為返回值是一種很差的風格 (這種問題可能發生的例子,請看 292 頁)。譯注:?**Appendix A, unexpected nil**?小節。 當你想要把一個數值與一系列的常量比較時,有?`case`?可以用。我們可以使用?`case`?來定義一個函數,返回每個月份中的天數: ~~~ (defun month-length (mon) (case mon ((jan mar may jul aug oct dec) 31) ((apr jun sept nov) 30) (feb (if (leap-year) 29 28)) (otherwise "unknown month"))) ~~~ 一個?`case`?表達式由一個實參開始,此實參會被拿來與每個子句的鍵值做比較。接著是零個或多個子句,每個子句由一個或一串鍵值開始,跟隨著零個或多個表達式。鍵值被視為常量;它們不會被求值。第一個參數的值被拿來與子句中的鍵值做比較 (使用?`eql`?)。如果匹配時,子句剩余的表達式會被求值,并將最后一個求值作為?`case`?的返回值。 缺省子句的鍵值可以是?`t`?或?`otherwise`?。如果沒有子句符合時,或是子句只包含鍵值時, ~~~ > (case 99 (99)) NIL ~~~ 則?`case`?返回?`nil`?。 `typecase`?宏與?`case`?相似,除了每個子句中的鍵值應為類型修飾符 (type specifiers),以及第一個實參與鍵值比較的函數使用`typep`?而不是?`eql`?(一個?`typecase`?的例子在 107 頁)。?**譯注: 6.5 小節。** ## 5.4 迭代 (Iteration)[](http://acl.readthedocs.org/en/latest/zhCN/ch5-cn.html#iteration "Permalink to this headline") 最基本的迭代操作符是?`do`?,在 2.13 小節介紹過。由于?`do`?包含了隱式的?`block`?及?`tagbody`?,我們現在知道是可以在?`do`?主體內使用?`return`?、?`return-from`?以及?`go`?。 2.13 節提到?`do`?的第一個參數必須是說明變量規格的列表,列表可以是如下形式: ~~~ (variable initial update) ~~~ `initial`?與?`update`?形式是選擇性的。若?`update`?形式忽略時,每次迭代時不會更新變量。若?`initial`?形式也忽略時,變量會使用`nil`?來初始化。 在 23 頁的例子中(譯注: 2.13 節), ~~~ (defun show-squares (start end) (do ((i start (+ i 1))) ((> i end) 'done) (format t "~A ~A~%" i (* i i)))) ~~~ `update`?形式引用到由?`do`?所創造的變量。一般都是這么用。如果一個?`do`?的?`update`?形式,沒有至少引用到一個?`do`?創建的變量時,反而很奇怪。 當同時更新超過一個變量時,問題來了,如果一個?`update`?形式,引用到一個擁有自己的?`update`?形式的變量時,它會被更新呢?或是獲得前一次迭代的值?使用?`do`?的話,它獲得后者的值: ~~~ > (let ((x 'a)) (do ((x 1 (+ x 1)) (y x x)) ((> x 5)) (format t "(~A ~A) " x y))) (1 A) (2 1) (3 2) (4 3) (5 4) NIL ~~~ 每一次迭代時,?`x`?獲得先前的值,加上一;?`y`?也獲得?`x`?的前一次數值。 但也有一個?`do*`?,它有著和?`let`?與?`let*`?一樣的關系。任何?`initial`?或?`update`?形式可以參照到前一個子句的變量,并會獲得當下的值: ~~~ > (do* ((x 1 (+ x 1)) (y x x)) ((> x 5)) (format t "(~A ~A) " x y)) (1 1) (2 2) (3 3) (4 4) (5 5) NIL ~~~ 除了?`do`?與?`do*`?之外,也有幾個特別用途的迭代操作符。要迭代一個列表的元素,我們可以使用?`dolist`?: ~~~ > (dolist (x '(a b c d) 'done) (format t "~A " x)) A B C D DONE ~~~ 當迭代結束時,初始列表內的第三個表達式 (譯注:?`done`?) ,會被求值并作為?`dolist`?的返回值。缺省是?`nil`?。 有著同樣的精神的是?`dotimes`?,給定某個?`n`?,將會從整數?`0`?,迭代至?`n-1`?: ~~~ (dotimes (x 5 x) (format t "~A " x)) 0 1 2 3 4 5 ~~~ `dolist`?與?```dotimes?初始列表的第三個表達式皆可省略,省略時為?``nil```?。注意該表達式可引用到迭代過程中的變量。 (譯注:第三個表達式即上例之?`x`?,可以省略,省略時?`dotimes`?表達式的返回值為?`nil`?。) do 的重點 (THE POINT OF do) 在 “The Evolution of Lisp” 里,Steele 與 Garbriel 陳述了 do 的重點, 表達的實在太好了,值得整個在這里引用過來: 撇開爭論語法不談,有件事要說明的是,在任何一個編程語言中,一個循環若一次只能更新一個變量是毫無用處的。 幾乎在任何情況下,會有一個變量用來產生下個值,而另一個變量用來累積結果。如果循環語法只能產生變量, 那么累積結果就得借由賦值語句來“手動”實現…或有其他的副作用。具有多變量的 do 循環,體現了產生與累積的本質對稱性,允許可以無副作用地表達迭代過程: ~~~ (defun factorial (n) (do ((j n (- j 1)) (f 1 (* j f))) ((= j 0) f))) ~~~ 當然在 step 形式里實現所有的實際工作,一個沒有主體的 do 循環形式是較不尋常的。 函數?`mapc`?和?`mapcar`?很像,但不會?`cons`?一個新列表作為返回值,所以使用的唯一理由是為了副作用。它們比?`dolist`?來得靈活,因為可以同時遍歷多個列表: ~~~ > (mapc #'(lambda (x y) (format t "~A ~A " x y)) '(hip flip slip) '(hop flop slop)) HIP HOP FLIP FLOP SLIP SLOP (HIP FLIP SLIP) ~~~ 總是返回?`mapc`?的第二個參數。 ## 5.5 多值 (Multiple Values)[](http://acl.readthedocs.org/en/latest/zhCN/ch5-cn.html#multiple-values "Permalink to this headline") 曾有人這么說,為了要強調函數式編程的重要性,每個 Lisp 表達式都返回一個值。現在事情不是這么簡單了;在 Common Lisp 里,一個表達式可以返回零個或多個數值。最多可以返回幾個值取決于各家實現,但至少可以返回 19 個值。 多值允許一個函數返回多件事情的計算結果,而不用構造一個特定的結構。舉例來說,內置的?`get-decoded-time`?返回 9 個數值來表示現在的時間:秒,分,時,日期,月,年,天,以及另外兩個數值。 多值也使得查詢函數可以分辨出?`nil`?與查詢失敗的情況。這也是為什么?`gethash`?返回兩個值。因為它使用第二個數值來指出成功還是失敗,我們可以在哈希表里儲存?`nil`?,就像我們可以儲存別的數值那樣。 `values`?函數返回多個數值。它一個不少地返回你作為數值所傳入的實參: ~~~ > (values 'a nil (+ 2 4)) A NIL 6 ~~~ 如果一個?`values`?表達式,是函數主體最后求值的表達式,它所返回的數值變成函數的返回值。多值可以原封不地通過任何數量的返回來傳遞: ~~~ > ((lambda () ((lambda () (values 1 2))))) 1 2 ~~~ 然而若只預期一個返回值時,第一個之外的值會被舍棄: ~~~ > (let ((x (values 1 2))) x) 1 ~~~ 通過不帶實參使用?`values`?,是可能不返回值的。在這個情況下,預期一個返回值的話,會獲得?`nil`?: ~~~ > (values) > (let ((x (values))) x) NIL ~~~ 要接收多個數值,我們使用?`multiple-value-bind`?: ~~~ > (multiple-value-bind (x y z) (values 1 2 3) (list x y z)) (1 2 3) > (multiple-value-bind (x y z) (values 1 2) (list x y z)) (1 2 NIL) ~~~ 如果變量的數量大于數值的數量,剩余的變量會是?`nil`?。如果數值的數量大于變量的數量,多余的值會被舍棄。所以只想印出時間我們可以這么寫: ~~~ > (multiple-value-bind (s m h) (get-decoded-time) (format t "~A:~A:~A" h m s)) "4:32:13" ~~~ 你可以借由?`multiple-value-call`?將多值作為實參傳給第二個函數: ~~~ > (multiple-value-call #'+ (values 1 2 3)) 6 ~~~ 還有一個函數是?`multiple-value-list`?: ~~~ > (multiple-value-list (values 'a 'b 'c)) (A B C) ~~~ 看起來像是使用?`#'list`?作為第一個參數的來調用?`multiple-value-call`?。 ## 5.6 中止 (Aborts)[](http://acl.readthedocs.org/en/latest/zhCN/ch5-cn.html#aborts "Permalink to this headline") 你可以使用?`return`?在任何時候離開一個?`block`?。有時候我們想要做更極端的事,在數個函數調用里將控制權轉移回來。要達成這件事,我們使用?`catch`?與?`throw`?。一個?`catch`?表達式接受一個標簽(tag),標簽可以是任何類型的對象,伴隨著一個表達式主體: ~~~ (defun super () (catch 'abort (sub) (format t "We'll never see this."))) (defun sub () (throw 'abort 99)) ~~~ 表達式依序求值,就像它們是在?`progn`?里一樣。在這段代碼里的任何地方,一個帶有特定標簽的?`throw`?會導致?`catch`?表達式直接返回: ~~~ > (super) 99 ~~~ 一個帶有給定標簽的?`throw`?,為了要到達匹配標簽的?`catch`?,會將控制權轉移 (因此殺掉進程)給任何有標簽的?`catch`?。如果沒有一個?`catch`?符合欲匹配的標簽時,?`throw`?會產生一個錯誤。 調用?`error`?同時中斷了執行,本來會將控制權轉移到調用樹(calling tree)的更高點,取而代之的是,它將控制權轉移給 Lisp 錯誤處理器(error handler)。通常會導致調用一個中斷循環(break loop)。以下是一個假定的 Common Lisp 實現可能會發生的事情: ~~~ > (progn (error "Oops!") (format t "After the error.")) Error: Oops! Options: :abort, :backtrace >> ~~~ 譯注:2 個?`>>`?顯示進入中斷循環了。 關于錯誤與狀態的更多訊息,參見 14.6 小節以及附錄 A。 有時候你想要防止代碼被?`throw`?與?`error`?打斷。借由使用?`unwind-protect`?,可以確保像是前述的中斷,不會讓你的程序停在不一致的狀態。一個?`unwind-protect`?接受任何數量的實參,并返回第一個實參的值。然而即便是第一個實參的求值被打斷時,剩下的表達式仍會被求值: ~~~ > (setf x 1) 1 > (catch 'abort (unwind-protect (throw 'abort 99) (setf x 2))) 99 > x 2 ~~~ 在這里,即便?`throw`?將控制權交回監測的?`catch`?,?`unwind-protect`?確保控制權移交時,第二個表達式有被求值。無論何時,一個確切的動作要伴隨著某種清理或重置時,?`unwind-protect`?可能會派上用場。在 121 頁提到了一個例子。 ## 5.7 示例:日期運算 (Example: Date Arithmetic)[](http://acl.readthedocs.org/en/latest/zhCN/ch5-cn.html#example-date-arithmetic "Permalink to this headline") 在某些應用里,能夠做日期的加減是很有用的 ── 舉例來說,能夠算出從 1997 年 12 月 17 日,六十天之后是 1998 年 2 月 15 日。在這個小節里,我們會編寫一個實用的工具來做日期運算。我們會將日期轉成整數,起始點設置在 2000 年 1 月 1 日。我們會使用內置的?`+`?與?`-`?函數來處理這些數字,而當我們轉換完畢時,再將結果轉回日期。 要將日期轉成數字,我們需要從日期的單位中,算出總天數有多少。舉例來說,2004 年 11 月 13 日的天數總和,是從起始點至 2004 年有多少天,加上從 2004 年到 2004 年 11 月有多少天,再加上 13 天。 有一個我們會需要的東西是,一張列出非潤年每月份有多少天的表格。我們可以使用 Lisp 來推敲出這個表格的內容。我們從列出每月份的長度開始: ~~~ > (setf mon '(31 28 31 30 31 30 31 31 30 31 30 31)) (31 28 31 30 31 30 31 31 30 31 30 31) ~~~ 我們可以通過應用?`+`?函數至這個列表來測試總長度: ~~~ > (apply #'+ mon) 365 ~~~ 現在如果我們反轉這個列表并使用?`maplist`?來應用?`+`?函數至每下一個?`cdr`?上,我們可以獲得從每個月份開始所累積的天數: ~~~ > (setf nom (reverse mon)) (31 30 31 30 31 31 30 31 30 31 28 31) > (setf sums (maplist #'(lambda (x) (apply #'+ x)) nom)) (365 334 304 273 243 212 181 151 120 90 59 31) ~~~ 這些數字體現了從二月一號開始已經過了 31 天,從三月一號開始已經過了 59 天……等等。 我們剛剛建立的這個列表,可以轉換成一個向量,見圖 5.1,轉換日期至整數的代碼。 ~~~ (defconstant month #(0 31 59 90 120 151 181 212 243 273 304 334 365)) (defconstant yzero 2000) (defun leap? (y) (and (zerop (mod y 4)) (or (zerop (mod y 400)) (not (zerop (mod y 100)))))) (defun date->num (d m y) (+ (- d 1) (month-num m y) (year-num y))) (defun month-num (m y) (+ (svref month (- m 1)) (if (and (> m 2) (leap? y)) 1 0))) (defun year-num (y) (let ((d 0)) (if (>= y yzero) (dotimes (i (- y yzero) d) (incf d (year-days (+ yzero i)))) (dotimes (i (- yzero y) (- d)) (incf d (year-days (+ y i))))))) (defun year-days (y) (if (leap? y) 366 365)) ~~~ **圖 5.1 日期運算:轉換日期至數字** 典型 Lisp 程序的生命周期有四個階段:先寫好,然后讀入,接著編譯,最后執行。有件 Lisp 非常獨特的事情之一是,在這四個階段時, Lisp 一直都在那里。可以在你的程序編譯 (參見 10.2 小節)或讀入時 (參見 14.3 小節) 來調用 Lisp。我們推導出?`month`?的過程演示了,如何在撰寫一個程序時使用 Lisp。 效率通常只跟第四個階段有關系,運行期(run-time)。在前三個階段,你可以隨意的使用列表擁有的威力與靈活性,而不需要擔心效率。 若你使用圖 5.1 的代碼來造一個時光機器(time machine),當你抵達時,人們大概會不同意你的日期。即使是相對近的現在,歐洲的日期也曾有過偏移,因為人們會獲得更精準的每年有多長的概念。在說英語的國家,最后一次的不連續性出現在 1752 年,日期從 9 月 2 日跳到 9 月 14 日。 每年有幾天取決于該年是否是潤年。如果該年可以被四整除,我們說該年是潤年,除非該年可以被 100 整除,則該年非潤年 ── 而要是它可以被 400 整除,則又是潤年。所以 1904 年是潤年,1900 年不是,而 1600 年是。 要決定某個數是否可以被另個數整除,我們使用函數?`mod`?,返回相除后的余數: ~~~ > (mod 23 5) 3 > (mod 25 5) 0 ~~~ 如果第一個實參除以第二個實參的余數為 0,則第一個實參是可以被第二個實參整除的。函數?`leap?`?使用了這個方法,來決定它的實參是否是一個潤年: ~~~ > (mapcar #'leap? '(1904 1900 1600)) (T NIL T) ~~~ 我們用來轉換日期至整數的函數是?`date->num`?。它返回日期中每個單位的天數總和。要找到從某月份開始的天數和,我們調用`month-num`?,它在?`month`?中查詢天數,如果是在潤年的二月之后,則加一。 要找到從某年開始的天數和,?`date->num`?調用?`year-num`?,它返回某年一月一日相對于起始點(2000.01.01)所代表的天數。這個函數的工作方式是從傳入的實參?`y`?年開始,朝著起始年(2000)往上或往下數。 ~~~ (defun num->date (n) (multiple-value-bind (y left) (num-year n) (multiple-value-bind (m d) (num-month left y) (values d m y)))) (defun num-year (n) (if (< n 0) (do* ((y (- yzero 1) (- y 1)) (d (- (year-days y)) (- d (year-days y)))) ((<= d n) (values y (- n d)))) (do* ((y yzero (+ y 1)) (prev 0 d) (d (year-days y) (+ d (year-days y)))) ((> d n) (values y (- n prev)))))) (defun num-month (n y) (if (leap? y) (cond ((= n 59) (values 2 29)) ((> n 59) (nmon (- n 1))) (t (nmon n))) (nmon n))) (defun nmon (n) (let ((m (position n month :test #'<))) (values m (+ 1 (- n (svref month (- m 1))))))) (defun date+ (d m y n) (num->date (+ (date->num d m y) n))) ~~~ **圖 5.2 日期運算:轉換數字至日期** 圖 5.2 展示了代碼的下半部份。函數?`num->date`?將整數轉換回日期。它調用了?`num-year`?函數,以日期的格式返回年,以及剩余的天數。再將剩余的天數傳給?`num-month`?,分解出月與日。 和?`year-num`?相同,?`num-year`?從起始年往上或下數,一次數一年。并持續累積天數,直到它獲得一個絕對值大于或等于?`n`?的數。如果它往下數,那么它可以返回當前迭代中的數值。不然它會超過年份,然后必須返回前次迭代的數值。這也是為什么要使用?`prev`?,`prev`?在每次迭代時會存入?`days`?前次迭代的數值。 函數?`num-month`?以及它的子程序(subroutine)?`nmon`?的行為像是相反地?`month-num`?。他們從常數向量?`month`?的數值到位置,然而`month-num`?從位置到數值。 圖 5.2 的前兩個函數可以合而為一。與其返回數值給另一個函數,?`num-year`?可以直接調用?`num-month`?。現在分成兩部分的代碼,比較容易做交互測試,但是現在它可以工作了,下一步或許是把它合而為一。 有了?`date->num`?與?`num->date`?,日期運算是很簡單的。我們在?`date+`?里使用它們,可以從特定的日期做加減。如果我們想透過`date+`?來知道 1997 年 12 月 17 日六十天之后的日期: ~~~ > (multiple-value-list (date+ 17 12 1997 60)) (15 2 1998) ~~~ 我們得到,1998 年 2 月 15 日。 ## Chapter 5 總結 (Summary)[](http://acl.readthedocs.org/en/latest/zhCN/ch5-cn.html#chapter-5-summary "Permalink to this headline") 1. Common Lisp 有三個基本的區塊建構子:?`progn`?;允許返回的?`block`?;以及允許?`goto`?的?`tagbody`?。很多內置的操作符隱含在區塊里。 2. 進入一個新的詞法語境,概念上等同于函數調用。 3. Common Lisp 提供了適合不同情況的條件式。每個都可以使用?`if`?來定義。 4. 有數個相似迭代操作符的變種。 5. 表達式可以返回多個數值。 6. 計算過程可以被中斷以及保護,保護可使其免于中斷所造成的后果。 ## Chapter 5 練習 (Exercises)[](http://acl.readthedocs.org/en/latest/zhCN/ch5-cn.html#chapter-5-exercises "Permalink to this headline") 1. 將下列表達式翻譯成沒有使用?`let`?與?`let*`?,并使同樣的表達式不被求值 2 次。 ~~~ (a) (let ((x (car y))) (cons x x)) (b) (let* ((w (car x)) (y (+ w z))) (cons w y)) ~~~ 1. 使用?`cond`?重寫 29 頁的?`mystery`?函數。(譯注: 第二章的練習第 5 題的 (b) 部分) 2. 定義一個返回其實參平方的函數,而當實參是一個正整數且小于等于 5 時,不要計算其平方。 3. 使用?`case`?與?`svref`?重寫?`month-num`?(圖 5.1)。 4. 定義一個迭代與遞歸版本的函數,接受一個對象 x 與向量 v ,并返回一個列表,包含了向量 v 當中,所有直接在?`x`?之前的對象: ~~~ > (precedes #\a "abracadabra") (#\c #\d #\r) ~~~ 1. 定義一個迭代與遞歸版本的函數,接受一個對象與列表,并返回一個新的列表,在原本列表的對象之間加上傳入的對象: ~~~ > (intersperse '- '(a b c d)) (A - B - C - D) ~~~ 1. 定義一個接受一系列數字的函數,并在若且唯若每一對(pair)數字的差為一時,返回真,使用 ~~~ (a) 遞歸 (b) do (c) mapc 與 return ~~~ 1. 定義一個單遞歸函數,返回兩個值,分別是向量的最大與最小值。 2. 圖 3.12 的程序在找到一個完整的路徑時,仍持續遍歷佇列。在搜索范圍大時,這可能會產生問題。 ~~~ (a) 使用 catch 與 throw 來變更程序,使其找到第一個完整路徑時,直接返回它。 (b) 重寫一個做到同樣事情的程序,但不使用 catch 與 throw。 ~~~
                  <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>

                              哎呀哎呀视频在线观看