Scheme 语言概要(下)
https://www.ibm.com/developerworks/cn/linux/l-schm/index2.html
談完了?scheme 的基本概念、數(shù)據(jù)類型和過程,我們接著介紹 scheme 的結(jié)構(gòu)、遞歸調(diào)用、變量和過程的綁定、輸入輸出等功能。
一.常用結(jié)構(gòu)
順序結(jié)構(gòu)
也可以說成由多個form組成的form,用begin來將多個form放在一對小括號內(nèi),最終形成一個form。格式為:(begin form1 form2 …)
如用Scheme語言寫成的經(jīng)典的helloworld程序是如下樣子的:
(begin (display "Hello world!") ; 輸出"Hello world!"(newline)) ; 換行if結(jié)構(gòu)
Scheme語言的if結(jié)構(gòu)有兩種格式,一種格式為:(if 測試 過程1 過程2),即測試條件成立則執(zhí)行過程1,否則執(zhí)行過程2。例如下面代碼:
(if (= x 0) (display "is zero") (display "not zero"))還有另一種格式:(if 測試 過程) ,即測試條件成立則執(zhí)行過程。例如下面代碼:
(if (< x 100) (display "lower than 100"))根據(jù)類型判斷來實現(xiàn)自省功能,下面代碼判斷給定的參數(shù)是否為字符串:
(define fun (lambda ( x )(if (string? x)(display "is a string")(display "not a string"))))如執(zhí)行 (fun 123) 則返回值為"not a string",這樣的功能在C++或JAVA中實現(xiàn)的話可能會很費力氣。
cond結(jié)構(gòu)
Scheme語言中的cond結(jié)構(gòu)類似于C語言中的switch結(jié)構(gòu),cond的格式為:
(cond ((測試) 操作) … (else 操作))如下是在Guile中的操作:
guile> (define w (lambda (x)(cond ((< x 0) 'lower)((> x 0) 'upper)(else 'equal)))) guile> w #<procedure w (x)> guile> (w 9) upper guile> (w -8) lower guile> (w 0) equal上面程序代碼中,我們定義了過程w,它有一個參數(shù)x,如果x的值大于0,則返回符號upper,如x的值小于0則返回符號lower,如x 的值為0則返回符號equal。
下載已做成可執(zhí)行腳本的?例程。
cond可以用if形式來寫,上面的過程可以如下定義:
guile> (define ff(lambda (x)(if (< x 0) 'lower(if (> x 0) 'upper 'zero)))) guile> ff #<procedure ff (x)> guile> (ff 9) upper guile> (ff -9) lower guile> (ff 0) zero這在功能上是和cond一樣的,可以看出cond實際上是實現(xiàn)了if的一種多重嵌套。
case結(jié)構(gòu)
case結(jié)構(gòu)和cond結(jié)構(gòu)有點類似,它的格式為:
(case (表達(dá)式) ((值) 操作)) ... (else 操作)))
case結(jié)構(gòu)中的值可以是復(fù)合類型數(shù)據(jù),如列表,向量表等,只要列表中含有表達(dá)式的這個結(jié)果,則進(jìn)行相應(yīng)的操作,如下面的代碼:
(case (* 2 3)((2 3 5 7) 'prime)((1 4 6 8 9) 'composite))上面的例子返回結(jié)果是composite,因為列表(1 4 6 8 9)中含有表達(dá)式(* 2 3)的結(jié)果6;下面是在Guile中定義的func過程,用到了case結(jié)構(gòu):
guile> (define func(lambda (x y)(case (* x y)((0) 'zero)(else 'nozero)))) guile> func #<procedure func (x y)> guile> (func 2 3) nozero guile> (func 2 0) zero guile> (func 0 9) zero guile> (func 2 9) nozero可以下載另一個腳本文件?te.scm,參考一下。
and結(jié)構(gòu)
and結(jié)構(gòu)與邏輯與運算操作類似,and后可以有多個參數(shù),只有它后面的參數(shù)的表達(dá)式的值都為#t時,它的返回值才為#t,否則為#f。看下面的操作:
guile> (and (boolean? #f) (< 8 12)) #t guile> (and (boolean? 2) (< 8 12)) #f guile> (and (boolean? 2) (> 8 12)) #f如果表達(dá)式的值都不是boolean型的話,返回最后一個表達(dá)式的值,如下面的操作:
guile> (and (list 1 2 3) (vector 'a 'b 'c)) #(a b c) guile> (and 1 2 3 4 ) 4 guile> (and 'e 'd 'c 'b 'a) aor結(jié)構(gòu)
or結(jié)構(gòu)與邏輯或運算操作類似,or后可以有多個參數(shù),只要其中有一個參數(shù)的表達(dá)式值為#t,其結(jié)果就為#t,只有全為#f時其結(jié)果才為#f。如下面的操作:
guile> (or #f #t) #t guile> (or #f #f) #f guile> (or (rational? 22/7) (< 8 12)) #t guile> (rational? 22/7) #t guile> (real? 22/7) #t guile> (or (real? 4+5i) (integer? 3.22)) #f我們還可以用and和or結(jié)構(gòu)來實現(xiàn)較復(fù)雜的判斷表達(dá)式,如在C語言中的表達(dá)式:
((x > 100) && (y < 100)) 和 ((x > 100) || (y > 100))在Scheme中可以表示為:
guile> (define x 123) guile> (define y 80) guile> (and (> x 100) (< y 100)) #t guile> (or (> x 100) (> y 100)) #tScheme語言中只有if結(jié)構(gòu)是系統(tǒng)原始提供的,其它的cond,case,and,or,另外還有do,when,unless等都是可以用宏定義的方式來定義的,這一點充分體現(xiàn)了Scheme的元語言特性,關(guān)于do,when等結(jié)構(gòu)的使用可以參考R5RS。
回頁首
二.遞歸調(diào)用
用遞歸實現(xiàn)階乘
在Scheme語言中,遞歸是一個非常重要的概念,可以編寫簡單的代碼很輕松的實現(xiàn)遞歸調(diào)用,如下面的階乘過程定義:
(define factoral (lambda (x)(if (<= x 1) 1(* x (factoral (- x 1))))))我們可以將下面的調(diào)用(factoral 4),即4的階乘的運算過程圖示如下:
以下為factoral過程在Guile中的運行情況:
guile> (define factoral (lambda (x) (if (<= x 1) 1 (* x (factoral (- x 1)))))) guile> factoral #<procedure factoral (x)> guile> (factoral 4) 24另一種遞歸方式
下面是一另一種遞歸方式的定義:
(define (factoral n)(define (iter product counter)(if (> counter n)product(iter (* counter product) (+ counter 1))))(iter 1 1)) (display (factoral 4))這個定義的功能和上面的完全相同,只是實現(xiàn)的方法不一樣了,我們在過程內(nèi)部實現(xiàn)了一個過程iter,它用counter參數(shù)來計數(shù),調(diào)用時從1開始累計,這樣它的展開過程正好和我們上面的遞歸過程的從4到1相反,而是從1到4。
循環(huán)的實現(xiàn)
在Scheme語言中沒有循環(huán)結(jié)構(gòu),不過循環(huán)結(jié)構(gòu)可以用遞歸來很輕松的實現(xiàn)(在Scheme語言中只有通過遞歸才能實現(xiàn)循環(huán))。對于用慣了C語言循環(huán)的朋友,在Scheme中可以用遞歸簡單實現(xiàn):
guile> (define loop(lambda(x y)(if (<= x y)(begin (display x) (display #\\space) (set! x (+ x 1))(loop x y))))) guile> loop #<procedure loop (x y)> guile> (loop 1 10) 1 2 3 4 5 6 7 8 9 10這只是一種簡單的循環(huán)定義,過程有兩個參數(shù),第一個參數(shù)是循環(huán)的初始值,第二個參數(shù)是循環(huán)終止值,每次增加1。相信讀者朋友一定會寫出更漂亮更實用的循環(huán)操作來的。
回頁首
三.變量和過程的綁定
let,let*,letrec
在多數(shù)編程語言中都有關(guān)于變量的存在的時限問題,Scheme語言中用let,let*和letrec來確定變量的存在的時限問題,即局部變量和全局變量,一般情況下,全局變量都用define來定義,并放在過程代碼的外部;而局部變量則用let等綁定到過程內(nèi)部使用。
用let可以將變量或過程綁定在過程的內(nèi)部,即實現(xiàn)局部變量:
guile> let #<primitive-macro! let>從上面的操作可以看出let是一個原始的宏,即guile內(nèi)部已經(jīng)實現(xiàn)的宏定義。
下面的代碼顯示了let的用法(注意多了一層括號):
guile> (let ((x 2) (y 5)) (* x y)) 10它的格式是:(let ((…)…) …),下面是稍復(fù)雜的用法:
guile> (let ((x 5))(define foo (lambda (y) (bar x y)))(define bar (lambda (a b) (+ (* a b) a)))(foo (+ x 3))) 45以上是Guile中的代碼實現(xiàn)情況。它的實現(xiàn)過程大致是:(foo 8) 展開后形成 (bar 5 8),再展開后形成 (+ (* 5 8) 5) ,最后其值為45。
再看下面的操作:
guile> (let ((iszero?(lambda(x)(if (= x 0) #t #f))))(iszero? 9)) #f guile> (iszero? 0) ;此時會顯示出錯信息let的綁定在過程內(nèi)有效,過程外則無效,這和上面提到的過程的嵌套定是一樣的,上面的iszero?過程在操作過程內(nèi)定義并使用的,操作結(jié)束后再另行引用則無效,顯示過程未定義出錯信息。
下面操作演示了let*的用法:
guile> (let ((x 2) (y 5))(let* ((x 6)(z (+ x y))) ;此時x的值已為6,所以z的值應(yīng)為11,如此最后的值為66(* z x))) 66還有l(wèi)etrec,看下面的操作過程:
guile> (letrec ((even?(lambda(x)(if (= x 0) #t(odd? (- x 1)))))(odd?(lambda(x)(if (= x 0) #f(even? (- x 1))))))(even? 88)) #t上面的操作過程中,內(nèi)部定義了兩個判斷過程even?和odd?,這兩個過程是互相遞歸引用的,如果將letrec換成let或let*都會不正常,因為letrec是將內(nèi)部定義的過程或變量間進(jìn)行相互引用的。看下面的操作:
guile> (letrec ((countdown(lambda (i)(if (= i 0) 'listoff(begin (display i) (display ",")(countdown (- i 1)))))))(countdown 10)) 10,9,8,7,6,5,4,3,2,1,listoffletrec幫助局部過程實現(xiàn)遞歸的操作,這不僅在letrec綁定的過程內(nèi),而且還包括所有初始化的東西,這使得在編寫較復(fù)雜的過程中經(jīng)常用到letrec,也成了理解它的一個難點。
apply
apply的功能是為數(shù)據(jù)賦予某一操作過程,它的第一個參數(shù)必需是一個過程,隨后的其它參數(shù)必需是列表,如:
guile> (apply + (list 2 3 4)) 9 guile> (define sum(lambda (x )(apply + x))) ; 定義求和過程 guile> sum #<procedure sum (x)> guile> (define ls (list 2 3 4 5 6)) guile> ls (2 3 4 5 6) guile> (sum ls) 20 guile> (define avg(lambda(x)(/ (sum x) (length x)))) ; 定義求平均過程 guile> avg #<procedure avg (x)> guile> (avg ls) 4以上定義了求和過程sum和求平均的過程avg,其中求和的過程sum中用到了apply來綁定"+"過程操作到列表,結(jié)果返回列表中所有數(shù)的總和。
map
map的功能和apply有些相似,它的第一個參數(shù)也必需是一個過程,隨后的參數(shù)必需是多個列表,返回的結(jié)果是此過程來操作列表后的值,如下面的操作:
guile> (map + (list 1 2 3) (list 4 5 6)) (5 7 9) guile> (map car '((a . b)(c . d)(e . f))) (a c e)除了apply,map以外,Scheme語言中還有很多,諸如:eval,delay,for-each,force,call-with-current-continuation等過程綁定的操作定義,它們都無一例外的提供了相當(dāng)靈活的數(shù)據(jù)處理能力,也就是另初學(xué)者望而生畏的算法,當(dāng)你仔細(xì)的體會了運算過程中用到的簡直妙不可言的算法后,你就會發(fā)現(xiàn)Scheme語言設(shè)計者的思想是多么偉大。
回頁首
四.輸入輸出
Scheme語言中也提供了相應(yīng)的輸入輸出功能,是在C基礎(chǔ)上的一種封裝。
端口
Scheme語言中輸入輸出中用到了端口的概念,相當(dāng)于C中的文件指針,也就是Linux中的設(shè)備文件,請看下面的操作:
guile> (current-input-port) #<input: standard input /dev/pts/0> ;當(dāng)前的輸入端口 guile> (current-output-port) #<output: standard output /dev/pts/0> ;當(dāng)前的輸出端口判斷是否為輸入輸出端口,可以用下面兩個過程:input-port? 和output-port? ,其中input-port?用來判斷是否為輸入端口,output-port?用來判斷是否為輸出端口。
open-input-file,open-output-file,close-input-port,close-output-port這四個過程用來打開和關(guān)閉輸入輸出文件,其中打開文件的參數(shù)是文件名字符串,關(guān)閉文件的參數(shù)是打開的端口。
輸入
打開一個輸入文件后,返回的是輸入端口,可以用read過程來輸入文件的內(nèi)容:
guile> (define port (open-input-file "readme")) guile> port #<input: readme 4> guile> (read port) GUILE語言上面的操作打開了readme文件,并讀出了它的第一行內(nèi)容。此外還可以直接用read過程來接收鍵盤輸入,如下面的操作:
guile> (read) ; 執(zhí)行后即等待鍵盤輸入 12345 12345 guile> (define x (read)) ; 等待鍵盤輸入并賦值給x 12345 guile> x 12345以上為用read來讀取鍵入的數(shù)字,還可以輸入字符串等其它類型數(shù)據(jù):
guile> (define name (read)) tomson guile> name tomson guile> (string? name) #f guile> (symbol? name) #t此時輸入的tomson是一個符號類型,因為字符串是用引號引起來的,所以出現(xiàn)上面的情況。下面因為用引號了,所以(string? str)返回值為#t 。
guile> (define str (read)) "Johnson" guile> str "Johnson" guile> (string? str) #t還可以用load過程來直接調(diào)用Scheme語言源文件并執(zhí)行它,格式為:(load "filename"),還有read-char過程來讀單個字符等等。
輸出
常用的輸出過程是display,還有write,它的格式是:(write 對象 端口),這里的對象是指字符串等常量或變量,端口是指輸出端口或打開的文件。下面的操作過程演示了向輸出文件temp中寫入字符串"helloworld",并分行的實現(xiàn)。
[root@toymouse test]# guile guile> (define port1 (open-output-file "temp")) ; 打開文件端口賦于port1 guile> port1 #<output: temp 3> guile> (output-port? port1) #t ; 此時證明port1為輸出端口 guile> (write "hello\\nworld" port1) guile> (close-output-port port1) guile> (exit) ; 寫入數(shù)據(jù)并關(guān)閉退出 [root@toymouse test]# more temp 顯示文件的內(nèi)容,達(dá)到測試目的 "hello world"在輸入輸出操作方面,還有很多相關(guān)操作,讀者可以參考R5RS的文檔。
回頁首
五.語法擴(kuò)展
Scheme語言可以自己定義象cond,let等功能一樣的宏關(guān)鍵字。標(biāo)準(zhǔn)的Scheme語言定義中用define-syntax和syntax-rules來定義,它的格式如下:
(define-syntax 宏名(syntax-rules()((模板) 操作)). . . ))下面定義的宏start的功能和begin相同,可以用它來開始多個塊的組合:
(define-syntax start(syntax-rules ()((start exp1)exp1)((start exp1 exp2 ...)(let ((temp exp1)) (start exp2 ...))) ))這是一個比較簡單的宏定義,但對理解宏定義來說是比較重要的,理解了他你才會進(jìn)一步應(yīng)用宏定義。在規(guī)則 ((start exp1) exp1) 中,(start exp1) 是一個參數(shù)時的模板,exp1是如何處理,也就是原樣搬出,不做處理。這樣 (start form1) 和 (form1) 的功能就相同了。
在規(guī)則 ((start exp1 exp2 ...) (let ((temp exp1)) (start exp2 ...))) 中,(start exp1 exp2 …) 是多個參數(shù)時的模板,首先用let來綁定局部變量temp為exp1,然后用遞歸實現(xiàn)處理多個參數(shù),注意這里說的是宏定義中的遞歸,并不是過程調(diào)用中的遞歸。另外在宏定義中可以用省略號(三個點)來代表多個參數(shù)。
在Scheme的規(guī)范當(dāng)中,將表達(dá)式分為原始表達(dá)式和有源表達(dá)式,Scheme語言的標(biāo)準(zhǔn)定義中只有原始的if分支結(jié)構(gòu),其它均為有源型,即是用后來的宏定義成的,由此可見宏定義的重要性。附上面的定義在GUILE中實現(xiàn)的?代碼。
回頁首
六. 其它功能
1. 模塊擴(kuò)展
在R5RS中并未對如何編寫模塊進(jìn)行說明,在諸多的Scheme語言的實現(xiàn)當(dāng)中,幾乎無一例外的實現(xiàn)了模塊的加載功能。所謂模塊,實際就是一些變量、宏定義和已命名的過程的集合,多數(shù)情況下它都綁定在一個Scheme語言的符號下(也就是名稱)。在Guile中提供了基礎(chǔ)的ice-9模塊,其中包括POSIX系統(tǒng)調(diào)用和網(wǎng)絡(luò)操作、正則表達(dá)式、線程支持等等眾多功能,此外還有著名的SFRI模塊。引用模塊用use-modules過程,它后面的參數(shù)指定了模塊名和我們要調(diào)用的功能名,如:(use-modules (ice-9 popen)),如此后,就可以應(yīng)用popen這一系統(tǒng)調(diào)用了。如果你想要定義自己的模塊,最好看看ice-9目錄中的那些tcm文件,它們是最原始的定義。
另外Guile在面向?qū)ο缶幊谭矫?#xff0c;開發(fā)了GOOPS(Guile Object-Oriented Programming System),對于喜歡OO朋友可以研究一下它,從中可能會有新的發(fā)現(xiàn)。
2. 如何輸出漂亮的代碼
如何編寫輸出漂亮的Scheme語言代碼應(yīng)該是初學(xué)者的第一個問題,這在Guile中可以用ice-9擴(kuò)展包中提供的pretty-print過程來實現(xiàn),看下面的操作:
guile> (use-modules (ice-9 pretty-print)) ; 引用漂亮輸出模塊 guile> (pretty-print '(define fix (lambda (n)(cond ((= n 0) 'iszero)((< n 0) 'lower)(else 'upper))))) ; 此處是我們輸入的不規(guī)則代碼 (define fix(lambda (n)(cond ((= n 0) 'iszero)((< n 0) 'lower)(else 'upper)))) ; 輸出的規(guī)則代碼3. 命令行參數(shù)的實現(xiàn)
在把Scheme用做shell語言時,經(jīng)常用到命令行參數(shù)的處理,下面是關(guān)于命令行參數(shù)的一種處理方法:
#! /usr/local/bin/guile -s !# (define cmm (command-line)) (display "應(yīng)用程序名稱:") (display (car cmm)) (newline) (define args (cdr cmm)) (define long (length args)) (define loop (lambda (count len obj)(if (<= count len)(begin(display "參數(shù) ")(display count)(display " 是:")(display (list-ref obj (- count 1)))(newline)(set! count (+ count 1))(loop count len obj))))) (loop 1 long args)下面是運行后的輸出結(jié)果:
[root@toymouse doc]# ./tz.scm abc 123 ghi 應(yīng)用程序名稱:./tz.scm 參數(shù) 1 是:abc 參數(shù) 2 是:123 參數(shù) 3 是:ghi其中最主要的是用到了command-line過程,它的返回結(jié)果是命令參數(shù)的列表,列表的第一個成員是程序名稱,其后為我們要的參數(shù),定義loop遞歸調(diào)用形成讀參數(shù)的循環(huán),顯示出參數(shù)值,達(dá)到我們要的結(jié)果。
4. 特殊之處
一些精確的自己計算自己的符號
數(shù)字 Numbers 2 ==> 2 字符串 Strings "hello" ==> "hello" 字符 Charactors #\\g ==> #\\g 輯值 Booleans #t ==> #t 量表 Vectors #(a 2 5/2) ==> #(a 2 5/2)通過變量計算來求值的符號
如:
x ==> 9-list ==> ("tom" "bob" "jim")factoral ==> #<procedure: factoral>==> #<primitive: +>define 特殊的form
(define x 9) ,define不是一個過程, 它是一個不用求所有參數(shù)值的特殊的form,它的操作步驟是,初始化空間,綁定符號x到此空間,然后初始此變量。
必須記住的東西
下面的這些定義、過程和宏等是必須記住的:
define,lambda,let,lets,letrec,quote,set!,if,case,cond,begin,and,or等等,當(dāng)然還有其它宏,必需學(xué)習(xí),還有一些未介紹,可參考有關(guān)資料。
走進(jìn)Scheme語言的世界,你就發(fā)現(xiàn)算法和數(shù)據(jù)結(jié)構(gòu)的妙用隨處可見,可以充分的檢驗?zāi)銓λ惴ê蛿?shù)據(jù)結(jié)構(gòu)的理解。Scheme語言雖然是古老的函數(shù)型語言的繼續(xù),但是它的里面有很多是在其它語言中學(xué)不到的東西,我想這也是為什么用它作為計算機(jī)語言教學(xué)的首選的原因吧。
總結(jié)
以上是生活随笔為你收集整理的Scheme 语言概要(下)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PEKS关键词可搜索加密详解
- 下一篇: 内联函数不能递归