這個函數用來比較2個對象的地址,若是相同的話就返回#t。在Scheme中真用#t表示,假則用#f。
例如,(eq? str str)返回#t,由於str自己的地址的是同樣的,可是」scheme」和」scheme」則被存儲在不一樣的地址中,所以函數返回#f。注意,不要用eq?來比較數字,由於在R5RS和MIT-Scheme中均沒有被指定返回值,建議使用eqv?或者=代替。如下是一些示例:markdown
(define str "scheme")
;Value: str
(eq? str str)
;Value: #t
(eq? "scheme" "scheme")
;Value:()
該函數比較2個儲存在內存中的對象的類型和值,若是類型和值都一致則返回#t。對於過程(lambda表達式)的比較依賴於具體的實現。這個函數不能用於相似於表和字符串一類的序列比較,由於儘管這些序列看起來是一致的,但它們存儲在不一樣的地址中。如下一樣是一些示例:函數
(eqv? 1.0 1.0)
;Value: #t
(eqv? 1 1.0)
;Value: ()
(eqv? (list 1 2 3) (list 1 23)) 不要去比較序列
;Value: ()
(eqv? "scheme" "scheme")
;Value: ()
比較序列就應該這個函數了。oop
(equal? (list 1 2 3) (list 1 2 3))
;Value: #t
(equal? "hello" "hello")
;Value #t
pair?若是對象爲序對則返回#t;post
list?若是對象是一個表則返回#t。要當心的是空表’()是一個表可是不是一個序對。學習
null?若是對象是空表’()的話就返回#t。ui
symbol?若是對象是一個符號則返回#t。spa
char?若是對象是一個字符則返回#t。.net
string?若是對象是一個字符串則返回#t。code
number?若是對象是一個數字則返回#t。對象
complex?若是對象是一個複數則返回#t。
real?若是對象是一個實數則返回#t。
rational?若是對象是一個有理數則返回#t。
integer?若是對象是一個整數則返回#t。
exact?若是對象不是一個浮點數的話則返回#t。
inexact?若是對象是一個浮點數的話則返回#t。
odd?若是對象是一個奇數的話則返回#t。
even?若是對象是一個偶數的話則返回#t。
postitive?若是對象是一個正數的話則返回#t。
negative?若是對象是一個負數的話則返回#t。
zero?若是對象是零的話則返回#t。
相似於用<=等比較數字,在比較字符的時候可使用char=?、char
在本身的定義中調用本身的函數叫作遞歸函數(Recursive Function)。想必你們都知道這些。
計算階乘則是演示遞歸的典型示例。
(define (fact n) (if (= n 1) 1 (* n(fact (- n 1)))))
所以(fact 5)的計算過程用如下方式能夠說明得很明顯。
(fact 5) => 5 * (fact 4) => 5 * 4 * (fact 3) => 5 * 4 * 3 * (fact 2) => 5 * 4 * 3 * 2 * (fact 1) => 5 * 4 * 3 * 2 * 1 => 5 * 4 * 3 * 2 => 5 * 4 * 6 => 5 * 24 => 120
但因爲(fact 5),(fact 4)…(fact 1)都被分配了不一樣的存儲空間,直到(fact(- i 1))返回一個值前,(fact i)都會被保存在內存中,因爲存在函數調用的開銷,這也就意味着會佔用更多的內存空間和計算時間。
這種時候,使用尾遞歸則包含了計算結果,當計算結束時直接將其返回。尤爲是Scheme規範要求尾遞歸調用轉化爲循環,所以尾遞歸調用就不存在函數調用開銷。如下就是fact函數的尾遞歸版本。
(define (fact-tail n) (fact-rec n n))
(define (fact-rec n p) (if (= n 1) p (let ((m (- n 1))) (fact-rec m (* p m)))))
同時,計算過程以下。
(fact-tail 5)
⇒(fact-rec 5 5)
⇒(fact-rec 4 20)
⇒(fact-rec 3 60)
⇒(fact-rec 2 120)
⇒(fact-rec 1 120)
⇒120
就是由於使用fact-rec並不用等待其它函數的計算結果,所以當它計算結束時即從內存中釋放。fact-rec的參數不斷在變化,這實際上就至關因而一種循環。就如同上文說到的,Scheme將尾遞歸轉化爲循環。
咱們先來看看2個遞歸的例子。
(define (my-length ls) (if (null? ls) 0 (+ 1 (my-length(cdr ls)))))
(define (remove x ls) (if (null? ls) '() (let ((h (car ls))) ((if (eqv? x h) (lambda (y) y) (lambda (y) (cons h y))) (remove x (cdr ls))))))
這2個例子的一個很明顯的區別在於它們各自最後讓遞歸過程中止的對象不一樣,前者是0,後者是’()。對於前者,咱們要算出的是ls中元素的個數,後者則是將ls中的x元素去掉。前者最後返回的是數,後者則是表。
下面咱們經過具體的示例來深刻了解這二者之間的關係。
求和由數構成的表中的元素
遞歸版:
(define (my-sum ls) (if (null? ls) 0 (+ (car ls) (my-sum (cdr ls)))))
尾遞歸版:
(define (my-sum-tail ls) (my-sum-rec ls 0))
(define (my-sum-rec ls n) (if (null? ls) n (my-sum-rec (cdr ls) (+ n (car ls)))))
前者最後當ls爲空是,返回0給+這個操做;後者的+操做則在my-sum-rec這個函數的參數位置,所以最後返回的是整個運算的結果n。前者經過不斷地加上(car ls)來達到最終的目的;後者則經過不斷的循環,將+操做的最終結果賦值給n。
named let也能夠用來表示循環,如下這個fact-let函數使用了一個loop,這和上文中的fact-rec函數是有很大區別的。在代碼的第二行,代碼將參數n1和p都初始化爲n。當每次循環後,參數都在最後一行進行更新,此處的更新爲:將n1減1,而將p乘以(n1-1)。
(define (fact-let n) (let loop ((n1 n)(p n)) (if(= n1 1) p (let ((m (- n1 1))) (loop m (* p m))))))
固然了,若是以爲有了一個let在這裏比較難以理解,下面這樣也是能夠的,不過上面這張方式更加簡潔明瞭罷了。
(define (fact-let n) (let loop ((n1 n) (p n)) (if (= n1 1) p (loop (- n1 1) (* p (- n1 1)))))
一樣,咱們經過對比遞歸來理解named let。
咱們要作的是經過函數求出x元素在ls中的位置,索引從0開始,若是不在ls中則返回#f。
遞歸版:
(define (position x ls) (position-aux x ls 0))
(define (position-aux x ls i) (cond ((null? ls) #f) ((eqv? x (car ls)) i) (else (position-aux x (cdr ls) (1+ i)))))
named let版:
(define (position x ls) (let loop((ls0 ls) (i 0)) (cond ((null? ls0)#f) ((eqv? x (car ls0)) i) (else (loop (cdr ls0)(1+ i))))))
後者就像嵌套通常,進入了遞歸版的第二行代碼。前者的else後面,經過函數調用返回自身;後者則很直接地經過更新參數來達到和遞歸一樣的目的。後者咱們先將ls賦值給ls0,經過不斷的(cdrls0)來更新,先將0賦值給i,經過不斷的(1+ i)來更新,這和遞歸中最後一行有着殊途同歸之妙。
下面咱們再經過尾遞歸來理解named let,多角度的對比,可以使咱們更清晰的理解和加深印象。
這些的示例都很簡單,基本上各大書籍文檔中都大同小異,下面咱們經過一個函數來反轉ls中的全部元素。
尾遞歸版:
(define (my-reverse ls) (my-reverse-rec ls ()))
(define (my-reverse-rec ls0 ls1) (if (null? ls0) ls1 (my-reverse-rec (cdr ls0) (cons (car ls0) ls1))))
named let版:
(define (my-reverse-letls) (let loop ((ls0 ls) (ls1 ())) (if (null? ls0) ls1 (loop (cdr ls0)(cons (car ls0) ls1)))))
咱們很容易的看到兩個版本的最後一行幾乎如出一轍;後者的第二行也至關於將前者的第二行代碼併到第三行代碼同樣。因而可知,named let也不過是個皮囊而已,正在的動力依舊來源於不斷的更新。
若是對named let感興趣的話,能夠看看這道題:【SICP練習】152 練習4.8。
letrec相似於let,不過它容許讓一個名字遞歸地調用它自身。一般letrec都用於定義複雜的遞歸函數。
依舊是這個經典的示例:
(define (fact-letrec n) (letrec ((iter (lambda (n1 p) (if (= n1 1) p (let ((m (- n1 1))) (iterm (* p m))))))) (iter n n)))
倒數第二行的代碼中,局部變量iter能夠在它的定義裏面引用它本身。而語法letrec是定義局部變量約定俗成的方式。
仍是老規矩,對比出真知,咱們先來看看上面第二大節中用來對比過的求和的例子。
我還在再次將它們貼出來好了。
尾遞歸版:
(define (my-sum-tail ls) (my-sum-rec ls0))
(define (my-sum-rec ls n) (if (null? ls) n (my-sum-rec (cdr ls) (+ n (car ls)))))
letrec版:
(define (my-sum-letrec ls) (letrec((iter(lambda(ls0 n) (if(null? ls0) n (iter (cdr ls0) (+ (car ls0) n)))))) (iter ls0)))
咱們能夠看出後者的最後一行的ls和0被賦值到第二行的ls0和n中,而後再倒數第二行中獲得更新。下面咱們再來看一個示例,這是將一個表明正整數的字符串轉化爲對應整數。例如「 3389」會被轉化爲3389。不過只是個示例而已,不須要去檢查那些不合法的輸入。符到整數的轉化是經過將字符#\0……#\9的ASCII減去48,可使用函數char->integer來得到字符的ASCII碼。函數string->list能夠將字符串轉化爲由字符構成的表。
尾遞歸版本:
(define (my-string->integer str) (char2int (string->list str) 0))
(define (char2int ls n) (if (null? ls) n (char2int (cdr ls) (+ (- (char->integer (car ls)) 48) (* n 10))))
named let版本:
(define (my-string->integer-letstr) (let loop ((ls0 (string->list str)) (n0)) (if (null? ls0) n (loop (cdr ls0) (+ (- (char->integer (car ls0)) 48) (* n 10))))))
letrec版本:
(define (my-string->integer-letrec str) (letrec ((iter (lambda (ls0 n) (if (null? ls0) n (iter (cdr ls0) (+ (- (char->integer (car ls0)) 48) (* n 10))))))) (iter (string->list str) 0)))
將尾遞歸中的第二行併到第三行就至關於named let版本的第二行了,更新的過程也大同小異。letrec版本的也和這個相似,將最後一行併到第二行也是同樣的,第五行到第七行均爲參數的更新,更新的過程也就是求解的過程。
就像在C系列語言中咱們一般用while比較多而do比較少同樣,在scheme中do也並不常見,但語法do也能夠用於表達重複。它的格式以下:
(do binds (predicate value) body)
變量在binds部分被綁定,而若是predicate被求值爲真,則函數從循環中逃逸(escape)出來,並返回值value,不然循環繼續進行。binds部分的格式以下所示:
[binds] - ((p1 i1 u1) (p2 i2u2)...)
變量p1,p2,…被分別初始化爲i1,i2,…並在循環後分別被更新爲u1,u2,…。
最後一次fact函數的do表達式版本。
(define (fact-do n) (do ((n1 n (- n1 1)) (p n (* p (- n1 1)))) ((= n1 1) p)))
變量n1和p分別被初始化爲n和n,在每次循環後分別被減去1和乘以(n1-1)。當n1變爲1時,函數返回p。此處的n1和p分別至關於上文中的p1和p2,n1後面的n和p後面的n分別至關於上文中的i1和i2,(- n1 1)和(* p (-n1 1))分別至關於上文中的u1和u2。
do也挺難的,不過我以爲letrec更加難以靈活運用。
下面咱們換一種方式,經過各個示例的do版本之間的聯繫來對比加深對這些語法的理解。
(define (my-reverse-do ls) (do ((ls0 ls (cdr ls0)) (ls1() (cons (car ls0) ls1))) ((null? ls0) ls1)))
(define (my-sum-do ls) (do ((ls0 ls (cdr ls0))(n0 (+ n (car ls0)))) ((null? ls0) n)))
(define (my-string->integer-do str) (do ((ls0 (string->list str)(cdr ls0)) (n0 (+ (- (char->integer (car ls0)) 48) (* n1 0)))) ((null? ls0) n)))
加色部分經過上文的講解相信很容易理解了,最後一行都是do的終止的判斷,爲真的時候則求值並返回最後一個ls1(或n)。
經過這一節的學習,相信你們都對講過的語法有了必定的理解,你們能夠利用這些函數來編寫本身的程序了。簡單的循環用let就夠了,至於複雜的局部遞歸函數則可使用letrec。至於do,若是可以靈活運用,相信也是威力無窮的。那麼,咱們下節見咯。
感謝訪問,但願對您有所幫助。 歡迎關注或收藏、評論或點贊。
爲使本文獲得斧正和提問,轉載請註明出處:
http://blog.csdn.net/nomasp