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

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                理解函數是理解 Lisp 的關鍵之一。概念上來說,函數是 Lisp 的核心所在。實際上呢,函數是你手邊最有用的工具之一。 [TOC] ## 6.1 全局函數 (Global Functions)[](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#global-functions "Permalink to this headline") 謂詞?`fboundp`?告訴我們,是否有個函數的名字與給定的符號綁定。如果一個符號是函數的名字,則?`symbol-function`?會返回它: ~~~ > (fboundp '+) T > (symbol-function '+) #<Compiled-function + 17BA4E> ~~~ 可通過?`symbol-function`?給函數配置某個名字: ~~~ (setf (symbol-function 'add2) #'(lambda (x) (+ x 2))) ~~~ 新的全局函數可以這樣定義,用起來和?`defun`?所定義的函數一樣: ~~~ > (add2 1) 3 ~~~ 實際上?`defun`?做了稍微多的工作,將某些像是 ~~~ (defun add2 (x) (+ x 2)) ~~~ 翻譯成上述的?`setf`?表達式。使用?`defun`?讓程序看起來更美觀,并或多或少幫助了編譯器,但嚴格來說,沒有?`defun`?也能寫程序。 通過把?`defun`?的第一個實參變成這種形式的列表?`(setf?f)`?,你定義了當?`setf`?第一個實參是?`f`?的函數調用時,所會發生的事情。下面這對函數把?`primo`?定義成?`car`?的同義詞: ~~~ (defun primo (lst) (car lst)) (defun (setf primo) (val lst) (setf (car lst) val)) ~~~ 在函數名是這種形式?`(setf?f)`?的函數定義中,第一個實參代表新的數值,而剩余的實參代表了傳給?`f`?的參數。 現在任何?`primo`?的?`setf`?,會是上面后者的函數調用: ~~~ > (let ((x (list 'a 'b 'c))) (setf (primo x) 480) x) (480 b c) ~~~ 不需要為了定義?`(setf?primo)`?而定義?`primo`?,但這樣的定義通常是成對的。 由于字符串是 Lisp 表達式,沒有理由它們不能出現在代碼的主體。字符串本身是沒有副作用的,除非它是最后一個表達式,否則不會造成任何差別。如果讓字符串成為?`defun`?定義的函數主體的第一個表達式, ~~~ (defun foo (x) "Implements an enhanced paradigm of diversity" x) ~~~ 那么這個字符串會變成函數的文檔字符串(documentation string)。要取得函數的文檔字符串,可以通過調用?`documentation`?來取得: ~~~ > (documentation 'foo 'function) "Implements an enhanced paradigm of diversity" ~~~ ## 6.2 局部函數 (Local Functions)[](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#local-functions "Permalink to this headline") 通過?`defun`?或?`symbol-function`?搭配?`setf`?定義的函數是全局函數。你可以像存取全局變量那樣,在任何地方存取它們。定義局部函數也是有可能的,局部函數和局部變量一樣,只在某些上下文內可以訪問。 局部函數可以使用?`labels`?來定義,它是一種像是給函數使用的?`let`?。它的第一個實參是一個新局部函數的定義列表,而不是一個變量規格說明的列表。列表中的元素為如下形式: ~~~ (name parameters . body) ~~~ 而?`labels`?表達式剩余的部份,調用?`name`?就等于調用?`(lambda?parameters?.?body)`?。 ~~~ > (labels ((add10 (x) (+ x 10)) (consa (x) (cons 'a x))) (consa (add10 3))) (A . 13) ~~~ `labels`?與?`let`?的類比在一個方面上被打破了。由?`labels`?表達式所定義的局部函數,可以被其他任何在此定義的函數引用,包括自己。所以這樣定義一個遞歸的局部函數是可能的: ~~~ > (labels ((len (lst) (if (null lst) 0 (+ (len (cdr lst)) 1)))) (len '(a b c))) 3 ~~~ 5.2 節展示了?`let`?表達式如何被理解成函數調用。?`do`?表達式同樣可以被解釋成調用遞歸函數。這樣形式的?`do`?: ~~~ (do ((x a (b x)) (y c (d y))) ((test x y) (z x y)) (f x y)) ~~~ 等同于 ~~~ (labels ((rec (x y) (cond ((test x y) (z x y)) (t (f x y) (rec (b x) (d y)))))) (rec a c)) ~~~ 這個模型可以用來解決,任何你對于?`do`?行為仍有疑惑的問題。 ## 6.3 參數列表 (Parameter Lists)[](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#parameter-lists "Permalink to this headline") 2.1 節我們演示過,有了前序表達式,?`+`?可以接受任何數量的參數。從那時開始,我們看過許多接受不定數量參數的函數。要寫出這樣的函數,我們需要使用一個叫做剩余(?*rest*?)參數的東西。 如果我們在函數的形參列表里的最后一個變量前,插入?`&rest`?符號,那么當這個函數被調用時,這個變量會被設成一個帶有剩余參數的列表。現在我們可以明白?`funcall`?是如何根據?`apply`?寫成的。它或許可以定義成: ~~~ (defun our-funcall (fn &rest args) (apply fn args)) ~~~ 我們也看過操作符中,有的參數可以被忽略,并可以缺省設成特定的值。這樣的參數稱為選擇性參數(optional parameters)。(相比之下,普通的參數有時稱為必要參數「required parameters」) 如果符號?`&optional`?出現在一個函數的形參列表時, ~~~ (defun philosoph (thing &optional property) (list thing 'is property)) ~~~ 那么在?`&optional`?之后的參數都是選擇性的,缺省為?`nil`?: ~~~ > (philosoph 'death) (DEATH IS NIL) ~~~ 我們可以明確指定缺省值,通過將缺省值附在列表里給入。這版的?`philosoph` ~~~ (defun philosoph (thing &optional (property 'fun)) (list thing 'is property)) ~~~ 有著更鼓舞人心的缺省值: ~~~ > (philosoph 'death) (DEATH IS FUN) ~~~ 選擇性參數的缺省值可以不是常量。可以是任何的 Lisp 表達式。若這個表達式不是常量,它會在每次需要用到缺省值時被重新求值。 一個關鍵字參數(keyword parameter)是一種更靈活的選擇性參數。如果你把符號?`&key`?放在一個形參列表,那在?`&key`?之后的形參都是選擇性的。此外,當函數被調用時,這些參數會被識別出來,參數的位置在哪不重要,而是用符號標簽(譯注:?`:`?)識別出來: ~~~ > (defun keylist (a &key x y z) (list a x y z)) KEYLIST > (keylist 1 :y 2) (1 NIL 2 NIL) > (keylist 1 :y 3 :x 2) (1 2 3 NIL) ~~~ 和普通的選擇性參數一樣,關鍵字參數缺省值為?`nil`?,但可以在形參列表中明確地指定缺省值。 關鍵字與其相關的參數可以被剩余參數收集起來,并傳遞給其他期望收到這些參數的函數。舉例來說,我們可以這樣定義?`adjoin`?: ~~~ (defun our-adjoin (obj lst &rest args) (if (apply #'member obj lst args) lst (cons obj lst))) ~~~ 由于?`adjoin`?與?`member`?接受一樣的關鍵字,我們可以用剩余參數收集它們,再傳給?`member`?函數。 5.2 節介紹過?`destructuring-bind`?宏。在通常情況下,每個模式(pattern)中作為第一個參數的子樹,可以與函數的參數列表一樣復雜: ~~~ (destructuring-bind ((&key w x) &rest y) '((:w 3) a) (list w x y)) (3 NIL (A)) ~~~ ## 6.4 示例:實用函數 (Example: Utilities)[](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#example-utilities "Permalink to this headline") 2.6 節提到過,Lisp 大部分是由 Lisp 函數組成,這些函數與你可以自己定義的函數一樣。這是程序語言中一個有用的特色:你不需要改變你的想法來配合語言,因為你可以改變語言來配合你的想法。如果你想要 Common Lisp 有某個特定的函數,自己寫一個,而這個函數會成為語言的一部分,就跟內置的?`+`?或?`eql`?一樣。 有經驗的 Lisp 程序員,由上而下(top-down)也由下而上 (bottom-up)地工作。當他們朝著語言撰寫程序的同時,也打造了一個更適合他們程序的語言。通過這種方式,語言與程序結合的更好,也更好用。 寫來擴展 Lisp 的操作符稱為實用函數(utilities)。當你寫了更多 Lisp 程序時,會發現你開發了一系列的程序,而在一個項目寫過許多的實用函數,下個項目里也會派上用場。 專業的程序員常發現,手邊正在寫的程序,與過去所寫的程序有很大的關聯。這就是軟件重用讓人聽起來很吸引人的原因。但重用已經被聯想成面向對象程序設計。但軟件不需要是面向對象的才能重用 ── 這是很明顯的,我們看看程序語言(換言之,編譯器),是重用性最高的軟件。 要獲得可重用軟件的方法是,由下而上地寫程序,而程序不需要是面向對象的才能夠由下而上地寫出。實際上,函數式風格相比之下,更適合寫出重用軟件。想想看?`sort`?。在 Common Lisp 你幾乎不需要自己寫排序程序;?`sort`?是如此的快與普遍,以致于它不值得我們煩惱。這才是可重用軟件。 ~~~ (defun single? (lst) (and (consp lst) (null (cdr lst)))) (defun append1 (lst obj) (append lst (list obj))) (defun map-int (fn n) (let ((acc nil)) (dotimes (i n) (push (funcall fn i) acc)) (nreverse acc))) (defun filter (fn lst) (let ((acc nil)) (dolist (x lst) (let ((val (funcall fn x))) (if val (push val acc)))) (nreverse acc))) (defun most (fn lst) (if (null lst) (values nil nil) (let* ((wins (car lst)) (max (funcall fn wins))) (dolist (obj (cdr lst)) (let ((score (funcall fn obj))) (when (> score max) (setf wins obj max score)))) (values wins max)))) ~~~ **圖 6.1 實用函數** 你可以通過撰寫實用函數,在程序里做到同樣的事情。圖 6.1 挑選了一組實用的函數。前兩個?`single?`?與?`append1`?函數,放在這的原因是要演示,即便是小程序也很有用。前一個函數?`single?`?,當實參是只有一個元素的列表時,返回真。 ~~~ > (single? '(a)) T ~~~ 而后一個函數?`append1`?和?`cons`?很像,但在列表后面新增一個元素,而不是在前面: ~~~ > (append1 '(a b c) 'd) (A B C D) ~~~ 下個實用函數是?`map-int`?,接受一個函數與整數?`n`?,并返回將函數應用至整數?`0`?到?`n-1`?的結果的列表。 這在測試的時候非常好用(一個 Lisp 的優點之一是,互動環境讓你可以輕松地寫出測試)。如果我們只想要一個?`0`?到?`9`?的列表,我們可以: ~~~ > (map-int #'identity 10) (0 1 2 3 4 5 6 7 8 9) ~~~ 然而要是我們想要一個具有 10 個隨機數的列表,每個數介于 0 至 99 之間(包含 99),我們可以忽略參數并只要: ~~~ > (map-int #'(lambda (x) (random 100)) 10) (85 50 73 64 28 21 40 67 5 32) ~~~ `map-int`?的定義說明了 Lisp 構造列表的標準做法(idiom)之一。我們創建一個累積器?`acc`?,初始化是?`nil`?,并將之后的對象累積起來。當累積完畢時,反轉累積器。?[[1]](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#id5) 我們在?`filter`?中看到同樣的做法。?`filter`?接受一個函數與一個列表,將函數應用至列表元素上時,返回所有非?`nil`?元素: ~~~ > (filter #'(lambda (x) (and (evenp x) (+ x 10))) '(1 2 3 4 5 6 7)) (12 14 16) ~~~ 另一種思考?`filter`?的方式是用通用版本的?`remove-if`?。 圖 6.1 的最后一個函數,?`most`?,根據某個評分函數(scoring function),返回列表中最高分的元素。它返回兩個值,獲勝的元素以及它的分數: ~~~ > (most #'length '((a b) (a b c) (a))) (A B C) 3 ~~~ 如果平手的話,返回先馳得點的元素。 注意圖 6.1 的最后三個函數,它們全接受函數作為參數。 Lisp 使得將函數作為參數傳遞變得便捷,而這也是為什么,Lisp 適合由下而上程序設計的原因之一。成功的實用函數必須是通用的,當你可以將細節作為函數參數傳遞時,要將通用的部份抽象起來就變得容易許多。 本節給出的函數是通用的實用函數。可以用在任何種類的程序。但也可以替特定種類的程序撰寫實用函數。確實,當我們談到宏時,你可以凌駕于 Lisp 之上,寫出自己的特定語言,如果你想這么做的話。如果你想要寫可重用軟件,看起來這是最靠譜的方式。 ## 6.5 閉包 (Closures)[](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#closures "Permalink to this headline") 函數可以如表達式的值,或是其它對象那樣被返回。以下是接受一個實參,并依其類型返回特定的結合函數: ~~~ (defun combiner (x) (typecase x (number #'+) (list #'append) (t #'list))) ~~~ 在這之上,我們可以創建一個通用的結合函數: ~~~ (defun combine (&rest args) (apply (combiner (car args)) args)) ~~~ 它接受任何類型的參數,并以適合它們類型的方式結合。(為了簡化這個例子,我們假定所有的實參,都有著一樣的類型。) ~~~ > (combine 2 3) 5 > (combine '(a b) '(c d)) (A B C D) ~~~ 2.10 小節提過詞法變量(lexical variables)只在被定義的上下文內有效。伴隨這個限制而來的是,只要那個上下文還有在使用,它們就保證會是有效的。 如果函數在詞法變量的作用域里被定義時,函數仍可引用到那個變量,即便函數被作為一個值返回了,返回至詞法變量被創建的上下文之外。下面我們創建了一個把實參加上?`3`?的函數: ~~~ > (setf fn (let ((i 3)) #'(lambda (x) (+ x i)))) #<Interpreted-Function C0A51E> > (funcall fn 2) 5 ~~~ 當函數引用到外部定義的變量時,這外部定義的變量稱為自由變量(free variable)。函數引用到自由的詞法變量時,稱之為閉包(closure)。?[[2]](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#id6)?只要函數還存在,變量就必須一起存在。 閉包結合了函數與環境(environment);無論何時,當一個函數引用到周圍詞法環境的某個東西時,閉包就被隱式地創建出來了。這悄悄地發生在像是下面這個函數,是一樣的概念: ~~~ (defun add-to-list (num lst) (mapcar #'(lambda (x) (+ x num)) lst)) ~~~ 這函數接受一個數字及列表,并返回一個列表,列表元素是元素與傳入數字的和。在 lambda 表達式里的變量?`num`?是自由的,所以像是這樣的情況,我們傳遞了一個閉包給?`mapcar`?。 一個更顯著的例子會是函數在被調用時,每次都返回不同的閉包。下面這個函數返回一個加法器(adder): ~~~ (defun make-adder (n) #'(lambda (x) (+ x n))) ~~~ 它接受一個數字,并返回一個將該數字與其參數相加的閉包(函數)。 ~~~ > (setf add3 (make-adder 3)) #<Interpreted-Function COEBF6> > (funcall add3 2) 5 > (setf add27 (make-adder 27)) #<Interpreted-Function C0EE4E> > (funcall add27 2) 29 ~~~ 我們可以產生共享變量的數個閉包。下面我們定義共享一個計數器的兩個函數: ~~~ (let ((counter 0)) (defun reset () (setf counter 0)) (defun stamp () (setf counter (+ counter 1)))) ~~~ 這樣的一對函數或許可以用來創建時間戳章(time-stamps)。每次我們調用?`stamp`?時,我們獲得一個比之前高的數字,而調用`reset`?我們可以將計數器歸零: ~~~ > (list (stamp) (stamp) (reset) (stamp)) (1 2 0 1) ~~~ 你可以使用全局計數器來做到同樣的事情,但這樣子使用計數器,可以保護計數器被非預期的引用。 Common Lisp 有一個內置的函數?`complement`?函數,接受一個謂詞,并返回謂詞的補數(complement)。比如: ~~~ > (mapcar (complement #'oddp) '(1 2 3 4 5 6)) (NIL T NIL T NIL T) ~~~ 有了閉包以后,很容易就可以寫出這樣的函數: ~~~ (defun our-complement (f) #'(lambda (&rest args) (not (apply f args)))) ~~~ 如果你停下來好好想想,會發現這是個非凡的小例子;而這僅是冰山一角。閉包是 Lisp 特有的美妙事物之一。閉包開創了一種在別的語言當中,像是不可思議的程序設計方法。 ## 6.6 示例:函數構造器 (Example: Function Builders)[](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#example-function-builders "Permalink to this headline") Dylan 是 Common Lisp 與 Scheme 的混合物,有著 Pascal 一般的語法。它有著大量返回函數的函數:除了上一節我們所看過的complement?,Dylan 包含:?`compose`?、?`disjoin`?、?`conjoin`?、?`curry`?、?`rcurry`?以及?`always`?。圖 6.2 有這些函數的 Common Lisp 實現,而圖 6.3 演示了一些從定義延伸出的等價函數。 ~~~ (defun compose (&rest fns) (destructuring-bind (fn1 . rest) (reverse fns) #'(lambda (&rest args) (reduce #'(lambda (v f) (funcall f v)) rest :initial-value (apply fn1 args))))) (defun disjoin (fn &rest fns) (if (null fns) fn (let ((disj (apply #'disjoin fns))) #'(lambda (&rest args) (or (apply fn args) (apply disj args)))))) (defun conjoin (fn &rest fns) (if (null fns) fn (let ((conj (apply #'conjoin fns))) #'(lambda (&rest args) (and (apply fn args) (apply conj args)))))) (defun curry (fn &rest args) #'(lambda (&rest args2) (apply fn (append args args2)))) (defun rcurry (fn &rest args) #'(lambda (&rest args2) (apply fn (append args2 args)))) (defun always (x) #'(lambda (&rest args) x)) ~~~ **圖 6.2 Dylan 函數建構器** 首先,?`compose`?接受一個或多個函數,并返回一個依序將其參數應用的新函數,即, ~~~ (compose #'a #'b #'c) ~~~ 返回一個函數等同于 ~~~ #'(lambda (&rest args) (a (b (apply #'c args)))) ~~~ 這代表著?`compose`?的最后一個實參,可以是任意長度,但其它函數只能接受一個實參。 下面我們建構了一個函數,先給取參數的平方根,取整后再放回列表里,接著返回: ~~~ > (mapcar (compose #'list #'round #'sqrt) '(4 9 16 25)) ((2) (3) (4) (5)) ~~~ 接下來的兩個函數,?`disjoin`?及?`conjoin`?同接受一個或多個謂詞作為參數:?`disjoin`?當任一謂詞返回真時,返回真,而?`conjoin`?當所有謂詞返回真時,返回真。 ~~~ > (mapcar (disjoin #'integerp #'symbolp) '(a "a" 2 3)) (T NIL T T) ~~~ ~~~ > (mapcar (conjoin #'integerp #'symbolp) '(a "a" 2 3)) (NIL NIL NIL T) ~~~ 若考慮將謂詞定義成集合,?`disjoin`?返回傳入參數的聯集(union),而?`conjoin`?則是返回傳入參數的交集(intersection)。 ~~~ cddr = (compose #'cdr #'cdr) nth = (compose #'car #'nthcdr) atom = (compose #'not #'consp) = (rcurry #'typep 'atom) <= = (disjoin #'< #'=) listp = (disjoin #'< #'=) = (rcurry #'typep 'list) 1+ = (curry #'+ 1) = (rcurry #'+ 1) 1- = (rcurry #'- 1) mapcan = (compose (curry #'apply #'nconc) #'mapcar complement = (curry #'compose #'not) ~~~ **圖 6.3 某些等價函數** 函數?`curry`?與?`rcurry`?(“right curry”)精神上與前一小節的?`make-adder`?相同。兩者皆接受一個函數及某些參數,并返回一個期望剩余參數的新函數。下列任一個函數等同于?`(make-adder?3)`?: ~~~ (curry #'+ 3) (rcurry #'+ 3) ~~~ 當函數的參數順序重要時,很明顯可以看出?`curry`?與?`rcurry`?的差別。如果我們?`curry?#'-`?,我們得到一個用其參數減去某特定數的函數, ~~~ (funcall (curry #'- 3) 2) 1 ~~~ 而當我們?`rcurry?#'-`?時,我們得到一個用某特定數減去其參數的函數: ~~~ (funcall (rcurry #'- 3) 2) -1 ~~~ 最后,?`always`?函數是 Common Lisp 函數?`constantly`?。接受一個參數并原封不動返回此參數的函數。和?`identity`?一樣,在很多需要傳入函數參數的情況下很有用。 ## 6.7 動態作用域 (Dynamic Sc??ope)[](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#dynamic-scope "Permalink to this headline") 2.11 小節解釋過局部與全局變量的差別。實際的差別是詞法作用域(lexical scope)的詞法變量(lexical variable),與動態作用域(dynamic scope)的特別變量(special variable)的區別。但這倆幾乎是沒有區別,因為局部變量幾乎總是是詞法變量,而全局變量總是是特別變量。 在詞法作用域下,一個符號引用到上下文中符號名字出現的地方。局部變量缺省有著詞法作用域。所以如果我們在一個環境里定義一個函數,其中有一個變量叫做?`x`?, ~~~ (let ((x 10)) (defun foo () x)) ~~~ 則無論?`foo`?被調用時有存在其它的?`x`?,主體內的?`x`?都會引用到那個變量: ~~~ > (let ((x 20)) (foo)) 10 ~~~ 而動態作用域,我們在環境中函數被調用的地方尋找變量。要使一個變量是動態作用域的,我們需要在任何它出現的上下文中聲明它是?`special`?。如果我們這樣定義?`foo`?: ~~~ (let ((x 10)) (defun foo () (declare (special x)) x)) ~~~ 則函數內的?`x`?就不再引用到函數定義里的那個詞法變量,但會引用到函數被調用時,當下所存在的任何特別變量?`x`?: ~~~ > (let ((x 20)) (declare (special x)) (foo)) 20 ~~~ 新的變量被創建出來之后, 一個?`declare`?調用可以在代碼的任何地方出現。?`special`?聲明是獨一無二的,因為它可以改變程序的行為。 13 章將討論其它種類的聲明。所有其它的聲明,只是給編譯器的建議;或許可以使程序運行的更快,但不會改變程序的行為。 通過在頂層調用?`setf`?來配置全局變量,是隱式地將變量聲明為特殊變量: ~~~ > (setf x 30) 30 > (foo) 30 ~~~ 在一個文件里的代碼,如果你不想依賴隱式的特殊聲明,可以使用?`defparameter`?取代,讓程序看起來更簡潔。 動態作用域什么時候會派上用場呢?通常用來暫時給某個全局變量賦新值。舉例來說,有 11 個變量來控制對象印出的方式,包括了`*print-base*`?,缺省是?`10`?。如果你想要用 16 進制顯示數字,你可以重新綁定?`*print-base*`?: ~~~ > (let ((*print-base* 16)) (princ 32)) 20 32 ~~~ 這里顯示了兩件事情,由?`princ`?產生的輸出,以及它所返回的值。他們代表著同樣的數字,第一次在被印出時,用 16 進制顯示,而第二次,因為在?`let`?表達式外部,所以是用十進制顯示,因為?`*print-base*`?回到之前的數值,?`10`?。 ## 6.8 編譯 (Compilation)[](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#compilation "Permalink to this headline") Common Lisp 函數可以獨立被編譯或挨個文件編譯。如果你只是在頂層輸入一個?`defun`?表達式: ~~~ > (defun foo (x) (+ x 1)) FOO ~~~ 許多實現會創建一個直譯的函數(interpreted function)。你可以將函數傳給?`compiled-function-p`?來檢查一個函數是否有被編譯: ~~~ > (compiled-function-p #'foo) NIL ~~~ 若你將?`foo`?函數名傳給?`compile`?: ~~~ > (compile 'foo) FOO ~~~ 則這個函數會被編譯,而直譯的定義會被編譯出來的取代。編譯與直譯函數的行為一樣,只不過對?`compiled-function-p`?來說不一樣。 你可以把列表作為參數傳給?`compile`?。這種?`compile`?的用法在 161 頁 (譯注: 10.1 小節)。 有一種函數你不能作為參數傳給?`compile`?:一個像是?`stamp`?或是?`reset`?這種,在頂層明確使用詞法上下文輸入的函數 (即?`let`?)?[[3]](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#id7)在一個文件里面定義這些函數,接著編譯然后載入文件是可以的。這么限制直譯的代碼的是實作的原因,而不是因為在詞法上下文里明確定義函數有什么問題。 通常要編譯 Lisp 代碼不是挨個函數編譯,而是使用?`compile-file`?編譯整個文件。這個函數接受一個文件名,并創建一個原始碼的編譯版本 ── 通常會有同樣的名稱,但不同的擴展名。當編譯過的文件被載入時,?`compiled-function-p`?應給所有定義在文件內的函數返回真。 當一個函數包含在另一個函數內時,包含它的函數會被編譯,而且內部的函數也會被編譯。所以?`make-adder`?(108 頁)被編譯時,它會返回編譯的函數: ~~~ > (compile 'make-adder) MAKE-ADDER > (compiled-function-p (make-adder 2)) T ~~~ ## 6.9 使用遞歸 (Using Recursion)[](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#using-recursion "Permalink to this headline") 比起多數別的語言,遞歸在 Lisp 中扮演了一個重要的角色。這主要有三個原因: 1. 函數式程序設計。遞歸演算法有副作用的可能性較低。 2. 遞歸數據結構。 Lisp 隱式地使用了指標,使得遞歸地定義數據結構變簡單了。最常見的是用在列表:一個列表的遞歸定義,列表為空表,或是一個?`cons`?,其中?`cdr`?也是個列表。 3. 優雅性。Lisp 程序員非常關心它們的程序是否美麗,而遞歸演算法通常比迭代演算法來得優雅。 學生們起初會覺得遞歸很難理解。但 3.9 節指出了,如果你想要知道是否正確,不需要去想遞歸函數所有的調用過程。 同樣的如果你想寫一個遞歸函數。如果你可以描述問題是怎么遞歸解決的,通常很容易將解法轉成代碼。要使用遞歸來解決一個問題,你需要做兩件事: 1. 你必須要示范如何解決問題的一般情況,通過將問題切分成有限小并更小的子問題。 2. 你必須要示范如何通過 ── 有限的步驟,來解決最小的問題 ── 基本用例。 如果這兩件事完成了,那問題就解決了。因為遞歸每次都將問題變得更小,而一個有限的問題終究會被解決的,而最小的問題僅需幾個有限的步驟就能解決。 舉例來說,下面這個找到一個正規列表(proper list)長度的遞歸算法,我們每次遞歸時,都可以找到更小列表的長度: 1. 在一般情況下,一個正規列表的長度是它的?`cdr`?加一。 2. 基本用例,空列表長度為?`0`?。 當這個描述翻譯成代碼時,先處理基本用例;但公式化遞歸演算法時,我們通常從一般情況下手。 前述的演算法,明確地描述了一種找到正規列表長度的方法。當你定義一個遞歸函數時,你必須要確定你在分解問題時,問題實際上越變越小。取得一個正規列表的?`cdr`?會給出?`length`?更小的子問題,但取得環狀列表(circular list)的?`cdr`?不會。 這里有兩個遞歸算法的示例。假定參數是有限的。注意第二個示例,我們每次遞歸時,將問題分成兩個更小的問題: 第一個例子,?`member`?函數,我們說某物是列表的成員,需滿足:如果它是第一個元素的成員或是?`member`?的?`cdr`?的成員。但空列表沒有任何成員。 第二個例子,?`copy-tree`?一個?`cons`?的?`copy-tree`?,是一個由?`cons`?的?`car`?的?`copy-tree`?與?`cdr`?的?`copy-tree`?所組成的。一個原子的?`copy-tree`?是它自己。 一旦你可以這樣描述算法,要寫出遞歸函數只差一步之遙。 某些算法通常是這樣表達最自然,而某些算法不是。你可能需要翻回前面,試試不使用遞歸來定義?`our-copy-tree`?(41 頁,譯注: 3.8 小節)。另一方面來說,23 頁 (譯注: 2.13 節) 迭代版本的?`show-squares`?可能更容易比 24 頁的遞歸版本要容易理解。某些時候是很難看出哪個形式比較自然,直到你試著去寫出程序來。 如果你關心效率,有兩個你需要考慮的議題。第一,尾遞歸(tail-recursive),會在 13.2 節討論。一個好的編譯器,使用循環或是尾遞歸的速度,應該是沒有或是區別很小的。然而如果你需要使函數變成尾遞歸的形式時,或許直接用迭代會更好。 另一個需要銘記在心的議題是,最顯而易見的遞歸算法,不一定是最有效的。經典的例子是費氏函數。它是這樣遞歸地被定義的, > 1. Fib(0) = Fib(1) = 1 > 2. Fib(n) = Fib(n-1)+Fib(n-2) 直接翻譯這個定義, ~~~ (defun fib (n) (if (<= n 1) 1 (+ (fib (- n 1)) (fib (- n 2))))) ~~~ 這樣是效率極差的。一次又一次的重復計算。如果你要找?`(fib?10)`?,這個函數計算?`(fib?9)`?與?`(fib?8)`?。但要計算出?`(fib?9)`,它需要再次計算?`(fib?8)`?,等等。 下面是一個算出同樣結果的迭代版本: ~~~ (defun fib (n) (do ((i n (- i 1)) (f1 1 (+ f1 f2)) (f2 1 f1)) ((<= i 1) f1))) ~~~ 迭代的版本不如遞歸版本來得直觀,但是效率遠遠高出許多。這樣的事情在實踐中常發生嗎?非常少 ── 這也是為什么所有的教科書都使用一樣的例子 ── 但這是需要注意的事。 ## Chapter 6 總結 (Summary)[](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#chapter-6-summary "Permalink to this headline") 1. 命名函數是一個存在符號的?`symbol-function`?部分的函數。?`defun`?宏隱藏了這樣的細節。它也允許你定義文檔字符串(documentation string),并指定?`setf`?要怎么處理函數調用。 2. 定義局部函數是有可能的,與定義局部變量有相似的精神。 3. 函數可以有選擇性參數(optional)、剩余(rest)以及關鍵字(keyword)參數。 4. 實用函數是 Lisp 的擴展。他們是由下而上編程的小規模示例。 5. 只要有某物引用到詞法變量時,它們會一直存在。閉包是引用到自由變量的函數。你可以寫出返回閉包的函數。 6. Dylan 提供了構造函數的函數。很簡單就可以使用閉包,然后在 Common Lisp 中實現它們。 7. 特別變量(special variable)有動態作用域 (dynamic scope)。 8. Lisp 函數可以單獨編譯,或(更常見)編譯整個文件。 9. 一個遞歸演算法通過將問題細分成更小丶更小的子問題來解決問題。 ## Chapter 6 練習 (Exercises)[](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#chapter-6-exercises "Permalink to this headline") 1. 定義一個?`tokens`?版本 (67 頁),接受?`:test`?與?`:start`?參數,缺省分別是?`#'constituent`?與?`0`?。(譯注: 67 頁在 4.5 小節) 2. 定義一個?`bin-search`?(60 頁)的版本,接受?`:key`?,?`:test`?,?`start`?與?`end`?參數,有著一般的意義與缺省值。(譯注: 60 頁在 4.1 小節) 3. 定義一個函數,接受任何數目的參數,并返回傳入的參數。 4. 修改?`most`?函數 (105 頁),使其返回 2 個數值,一個列表中最高分的兩個元素。(譯注: 105 頁在 6.4 小節) 5. 用?`filter`?(105 頁) 來定義?`remove-if`?(沒有關鍵字)。(譯注: 105 頁在 6.4 小節) 6. 定義一個函數,接受一個參數丶一個數字,并返回目前傳入參數中最大的那個。 7. 定義一個函數,接受一個參數丶一個數字,若傳入參數比上個參數大時,返回真。函數第一次調用時應返回?`nil`?。 8. 假設?`expensive`?是一個接受一個參數的函數,一個介于 0 至 100 的整數(包含 100),返回一個耗時的計算結果。定義一個函數`frugal`?來返回同樣的答案,但僅在沒見過傳入參數時調用?`expensive`?。 9. 定義一個像是?`apply`?的函數,但在任何數字印出前,缺省用 8 進制印出。 腳注 [[1]](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#id2) | 在這個情況下,?`nreverse`?(在 222 頁描述)和?`reverse`?做一樣的事情,但更有效率。 [[2]](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#id3) | “閉包”這個名字是早期的 Lisp 方言流傳而來。它是從閉包需要在動態作用域里實現的方式衍生而來。 [[3]](http://acl.readthedocs.org/en/latest/zhCN/ch6-cn.html#id4) | 以前的 ANSI Common Lisp,?`compile`?的第一個參數也不能是一個已經編譯好的函數。
                  <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>

                              哎呀哎呀视频在线观看