日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Scheme语言深入

發布時間:2023/12/20 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Scheme语言深入 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文轉自:http://www.ibm.com/developerworks/cn/linux/l-schm/part3/


一、關于符號類型

符號類型又稱引用類型,在概要一文中本人介紹得非常的模糊,使很多初學者不理解。符號類型在Scheme語言中是最基礎也是最重要的一種類型,這是因為Scheme語言的祖先Lisp語言的最初目的就是符號處理,在Scheme語言中幾乎所有的東西都可以看做是符號或做為符號列表來處理,這也是我們把符號類型做為第一個問題研究的原因。

與符號類型相關的關鍵字有四個,分別是:quote, quasiquote, unquote和unquote-splicing,如下所示:

規范用法:(quote obj)
簡化用法:'obj (注意,'為右單引號,"雙引號下面的那個符號。)
意義:符號類型的定義,(quote obj)本身就是一個值,雖然它不如數字123這樣直觀。

規范用法:(quasiquote obj)
簡化用法:`obj (注意,`為左單引號,~波浪號下面的那個符號。)
意義:"類似符號"類型的定義,最好稱之為逆符號類型,它可以將符號類型轉換為具有實際意義的東西。

規范用法:(unquote obj)
簡化用法:,obj (注意,,逗號,<小于號下面的那個符號。)
意義:"非符號"類型的定義,非符號類型出現在符號類型或逆符號類型定義中間,它不直接做為符號類型使用,而是將運算結果做為符號類型的一部分。

規范用法:(unquote-splicing obj)
簡化用法:,@obj
意義:非符號類型的拼接,注意:,@ 兩個符號做為一個操作符來使用)。當非符號類型是一些復雜算法時,需要用它來做一下拼接,以達到符號類型的目的。
上面所說的所有規范用法和簡化用法的功能都是相同的。

符號類型的意義在于,一個說明,英文單詞zebra指的是活生生的斑馬,而'zebra或(quote zebra)指的是由字母z、e、b、r、a構成的這串符號(不是字符串),就象我們定義變量(define x 100),這時x指的就是100這個數值,而'x或(quote x)則代表字母x構成的這個符號。

首先看一段代碼:

guile> (define s '(good morning)) guile> s (good morning) guile> (symbol? s) #f guile> (list? s) #t guile> (symbol? (list-ref s 1)) #t

從此示例中可以看出,用quote定義的列表的類型仍是列表,而列表中的某一值的類型則是符號類型。還可以看出有點類似于如下:

(+ 1 (+ 2 (+ 3 (+ 4 5)))) ==> (+ 1 2 3 4 5) (list 'a 'b 'c 'd 'e) ==> '(a b c d e)

兩者有異曲同工之妙,減少了多余的操作符,使表達式更直觀,更容易理解。

從 '(1 2 3 4 5) ==> (1 2 3 4 5) 可以看出,由符號類型的定義來形成列表,這是Scheme語言繼承自LISP語言的傳統。

下面是在guile中的用法示例:

guile> `(1 ,(+ 1 1) 3) (1 2 3) guile> (quasiquote (1 (unquote (+ 1 1)) 3)) (1 2 3) ;;;第一個是簡化用法,第二個是標準用法。 guile> `(1 ,@(map + '(1 3) '(2 4)) 9) (1 3 7 9) guile> (quasiquote (1 (unquote-splicing (map + (quote (1 3)) (quote (2 4)))) 9)) (1 3 7 9) ;;;第一個是簡化用法,第二個是標準用法(注意:,@ 兩個符號做為一個操作符來使用)。

從示例中我們可以看出,這些應用多數與列表有關,而處理列表是Scheme語言的關鍵所在。符號類型的用法對深入理解Scheme語言也非常關鍵,因為Scheme語言本身就可以理解為是這種符號類型的列表,處理符號類型就是處理Scheme語言本身。

二、關于尾遞歸

數列問題是研究遞歸的非常好的范例,在王垠的主頁中有關于用菲波那契數列來說明非尾遞歸與尾遞歸之間區別和尾遞歸的好處的一個例子,見 http://learn.tsinghua.edu.cn/homepage/2001315450/wiki/TailRecursion.html 。我們這里用更簡單一點的問題,求累計的問題來說明,即求自然數1+2+3+4+ ... +n的和。事實上就是設計一個過程,給它一個參數n,求1+2+3+ ... +n的和,我們首設計一個suma過程,代碼如下:

#! /usr/local/bin/guile -s !# (define suma(lambda (n)(if (= n 1)1(+ n (suma (- n 1)))))) (display "(suma 100) ==> ") (display (suma 100)) (newline) (display "(suma 818) ==> ") (display (suma 818)) (newline) 運行這段代碼,會出現如下結果: (suma 100) ==> 5050 (suma 818) ==> 334971

說明:
以(suma 5)為例,表達式展開后:

(suma 5) (+ 5 (suma 4)) (+ 5 4 (suma 3)) (+ 5 4 3 (suma 2)) (+ 5 4 3 2 (suma 1)) (+ 5 4 3 2 1) ==> 15

如果n為1000的話則會最終形成(+ 1000 999 ... 3 2 1)這樣長度驚人的表達式,結果直接導致guile的崩潰。

為什么會是818呢?因為819是會溢出的,出錯,得不到結果,這可能大出乎我們意料之外,因為如果用C來寫這樣功能的程序代碼,可能會求到6位數而不出問題。這一過程是用非尾遞歸來完成的,它的擴張呈指數級增長。代碼的迅速膨脹,使guile沒有處理到1000就崩潰了。

我們再來看看采用尾遞歸的情況,代碼如下:

#! /usr/local/bin/guile -s !# (define sumb(lambda (n)(let f ((i n) (a 1))(if (= i 1)a(f (- i 1) (+ a i)))))) (display "(sumb 100) ==> ") (display (sumb 100)) (newline) (display "(sumb 1000) ==> ") (display (sumb 1000)) (newline) 運行結果如下: (sumb 100) ==> 5050 (sumb 1000) ==> 500500

還是以n為5的情況來說明:

(sumb 5) (f 5 1) (f 4 6) (f 3 10) (f 2 13) (f 1 15) ==> 15

這樣的話,始終是依次計算,不會出現列表膨脹,所以n為1000時也不會出錯,計算速度也很快。

此結果大超出了非尾遞歸的818限制,參數是10000也沒問題,因它采用尾遞歸,代碼根本沒有膨脹,而是依次計算。首先是在過程內部綁定了一個過程f,它有兩個參數,一個i的值來自sum過程的參數n,另一個參數a定義值為1,當i值不等于時,仍調用f,第一個參數(也就是i)減1,第二個參數(也就是a)的值在原來的基礎上加上i,當i的值為1是返回a,也就此sum過程的結果。理解這些后,你會發現事實上尾遞歸是在過程的綁定和過程的參數上做文章,用參數來保存運算結果,遞歸調用綁定的過程,最終達到運算目的。

三、關于過程參數的問題

過程的多參數問題對初學者不太好理解,一般情況下我們處理過程時,過程參數的數量是固定的,當過程的參數數量不固定時怎么辦呢?對了,時刻記住列表,把過程的參數做為一個列表來處理,如求和過程:(sum arg1 arg2 ...),(初學者可能對如何實現定義這樣的過程無從下手不知所措),我們如何來求這些參數的和呢?看下面的代碼:

guile> (define sum (lambda args (apply + args))) guile> sum #<procedure sum args> guile> (sum 1 2 3 4 5) 15

從中可以看出,lambda的格式有所變化,由原來的((lambda arg1 arg2 ...) body ...)變成了(lambda args body ...),也就是將原來的多個項組成的列表,改成了用一個名稱args來標識的列表,其本質并沒有變,變的是我們的方法,由原來的單項處理變成了統一處理的列表。

這里還用到了for-each過程,通過下面代碼來看一下for-each過程的一般用法:

guile> (define newdisplay (lambda (x) (begin (display x)(newline)))) guile> newdisplay #<procedure newdisplay (x)> guile> (define tt (lambda args (for-each newdisplay args))) guile> tt #<procedure tt args> guile> (tt 'abc 'efg 'tomson) abc efg tomson

for-each過程的一般用法是(for-each 過程 列表),此中的過程可以是我們自定義的,也可以是系統提供的,還可以是lambda 表達式。

棧結構是一種簡單而又有意義的數據結構,我們可以用列表來模擬一個簡單的棧,下面是代碼:

#! /usr/local/bin/guile -s !# (define stack '()) (define push!(lambda (x)(set! stack (cons x stack)))) (define pop!(lambda ()(let ((temp (car stack)))(set! stack (cdr stack))temp))) (push! 9) (push! 8) (push! 7) (display stack) (newline) (display (pop!)) (newline) (display stack) (newline)

結果如下:

(7 8 9) 7 (8 9)

這里面我們定義了一個變量stack來表示棧,定義一個過程push!向棧內壓數據,同時還定義了一個過程pop!來從棧內彈出數據,完成了基本的棧功能。這段代碼的缺點是要定義一個外部的變量來表示棧,同時還有兩個過程,如果創建多個棧的話就需要更多的過程和變量了,這在某些情況下是不可想象的,如果程序中要用100個棧,我們就不得不100次復制和更改上面的代碼。如何解決這一問題呢?看下面的代碼:

#! /usr/local/bin/guile -s !# (define make-stack(lambda ()(let ((st '()))(lambda (process arg)(case process((push!) (begin(set! st (cons arg st))st))((pop!) (let ((temp (car st)))(set! st (cdr st))temp))((view) (display st))(else "error!")))))) (define s (make-stack)) (display (s 'push! 9)) (newline) (display (s 'push! 8)) (newline) (display (s 'push! 7)) (newline) (display (s 'pop! 0)) (newline) (s 'view 0) (newline)

結果如下:

(9) (8 9) (7 8 9) 7 (8 9)

在上面代碼中定義的make-stack過程,它的形式是一種特殊的情況,在lambda表達式里面又嵌有lambda表達式,在使用這類過程時,先要調用這一過程定義一個變量(這個變量其實就是第二個lambda表達式),然后將這個變量再做為一個過程來直接調用(事實上也就是生成了一個過程),就像代碼中的(s 'push 9) 。

我們首先綁定了一個變量st為空值做為棧的基礎,與棧有關的操作都圍繞它展開,這樣的話前面提到代碼重復問題就不會出現了,你可以定義任意多個棧。這段代碼里還用到了case結構,它有點像C語言中的switch語句,用它判斷第二個lambda表達式的第一個參數,也就是要對棧的操作,在調用時要用符號變量來使用,否則會出錯,因為'push結果就是push,所以在過程定義中直接使用push,而調用時用'push。從這段代碼中你會意識到變量和符號的重要性了。

這段代碼中我們仍用上面代碼的形式,用列表來模擬棧,因為這更能體現棧的原理和列表這一Scheme語言的基礎數據類型的做用。細心的讀者朋友會發現我們對棧進行pop操作時的調用是(s 'pop! 0),而正確的操作應該是(s 'pop!),'view也同樣;這是因為我們第二個lambda表達式是lambda (process arg),為了不出錯,不得不用這樣的調用,如果將第二個lambda表達式改為lambda (process . arg)就可以避免這種尷尬情況了,但結果可能并不是我們想要的,棧會變成((7) (8) (9))這種情況。

如何更好的實現一個棧呢?對了,改變現有的形式,使用list(不用原始的cons)或vector來模擬,將 lambda (process arg) 改成 (process . arg) 或 (process . args) (注意,這兩者可不一樣啊!),這就要看你對棧結構的理解和編碼水平了,相信參照這一代碼你會模擬出更實用更快捷的棧結構來的。(本文代碼中有一個用list來模擬棧結構的稍完整的代碼)這里我們還會發現一個小小的慣例,如果過程要更改變量的值,那么它的過程名后一定要加一個!;而如果過程是一個判斷,結果為邏輯值時,它的過程名后一定要加一個?,這會使別人很快理解你的代碼。

四、關于continuation

Scheme語言相對Lisp語言的重要特征是提出并實現了continuation,這是讓初學者最難理解,也是最讓程序員心動的特征。那么Continuation到底是什么呢?在運算Scheme表達式過程中,我們必須跟蹤兩個東西:1、運算什么?2、什么與值相關?看一看下面表達式的計算: (if (null? x) (quote ()) (car x)) 。其中首先要運算的是(null? x),然后在這個值的基礎上再運算(quote ())或(cdr x),要運算的是(null? x)而與值相關的是(quote ())和(car x);這里把與值相關的稱為運算的continuation。我們看(null? x)表達式中與值相關的東西就有三個,1、表達式本身,結果為#t或#f;2、過程null?,它是一個lambda表達式;3、變量x,它應用當有一個值。那么上面的表達式里面有幾個continuation呢?答案是六個,上面的表達式本身,我們說過的三個,car也是一個lambda表達式,還有它后面的x,也和值相關;而(car x)沒有計算在內,因為它與上面表達式結果之一是相同的。

在任何表達式中,Continuation可以用一個叫call-with-current-continuation的過程來得到,多數情況下它被簡化為call/cc。在guile的1.6.4或以前版本中,簡化代碼如下所示:

(define call/cc call-with-current-continuation)

而在其它的Scheme語言的實現版本中完全可以不用這樣定義,而直接使用call/cc。

call/cc過程的唯一個參數應該是一個過程(或lambda表達式),而且這個過程只能有一個參數,continuation就綁定在這個參數上。看下面的代碼:

guile> (define call/cc call-with-current-continuation) guile> call/cc #<procedure call-with-current-continuation (proc)> guile> (call/cc (lambda (k) 5)) 5 ;;;此時過程參數k未用到,所以取過程的返回值5做為結果 guile> (call/cc (lambda (k) (* 5 (k 8)))) 8 ;;;此時過程參數k綁定為8,所以其結果為8 guile> (+ 2 (call/cc (lambda (k) (* 5 (k 8))))) 10 ;;;此時結果在我們意料之中了

可以利用call/cc這一特點,讓它從一個循環中跳出來,這有點像C語言中的break,看下面的代碼:

guile> (call/cc (lambda (return)(for-each (lambda (x) (if (< x 0) (return x)))'(99 88 77 66 55))#t)) #t

其結果為#t,因為for-each運算過程中未出現過(< x 0)的情況,所以(lambda (return ) ...)的結果為#t,call/cc取此值為最終結果。

guile> (call/cc (lambda (return)(for-each (lambda (x) (if (< x 0) (return x)))'(11 22 33 44 -55 66 77))#t)) -55

其結果為-55,因為當遇到小于0的數時,return就綁定x,call/cc就返回此值,即從for-each處理過程中跳出來,這是call/cc的重要功能之一,也是最基本的功能。

call/cc 還可以這樣操作:

guile> (define foo #f) guile> (call/cc (lambda (bar) (set! foo bar) 123)) 123 guile> (foo 456) 456 guile> (foo 'abc) abc

上面的操作中,call/cc綁定了lambda表達式的參數bar,而后我們又設定foo的值為bar,此時foo即為一個靈活的continuation;最后我們又讓lambda表達式的值為123,所以整個call/cc表達式的值為123,如果我們不加這個123而直接結束這個表達式的話,此表達式就沒有結果,不返回任何值。

當foo成為一個continuation后,我們可以象調用過程那樣來調用它(當然它并不是過程),其后面綁定的值即為此continuation的結果,如上面的代碼運行所示。

這段代碼的前提是有call/cc的定義,如果你直接運行guile后就輸入上面的代碼是肯定會出錯的。

在趙蔚的文章 http://www.ibm.com/developerworks/cn/linux/l-scheme/part2/index.shtml中提到過由David Madore先生提出的"陰陽迷題",下面我們通過對它的研究來理解一下continuation,代碼如下所示(為解釋方便,稍做改動):

(let* ((yin ((lambda (foo) (display "@") foo) (call/cc (lambda (bar) bar))))(yang ((lambda (foo) (display "*") foo) (call/cc (lambda (bar) bar)))))(yin yang))

我們進行一些簡化,將其中的lambda表達式定義為過程,使其看起來更清晰:

(define bar (lambda (bar) bar)) (define foox (lambda (foo) (display "@") foo)) (define fooy (lambda (foo) (display "*") foo))

則上面的繁瑣的表達式可以變成為:

(let* ((yin (foox (call/cc bar)))(yang (fooy (call/cc bar))))(yin yang))

將let*改變成let,使其進一步簡化為:

(let ((yin (foox (call/cc bar))))(let ((yang (fooy (call/cc bar))))(yin yang)))

最后將let去掉,繼而成為:

((foox (call/cc bar)) (fooy (call/cc bar)))

經過這一翻簡化處理(對初學者是有必要的),相信我們對Scheme語言會有新的認識。需要說明的是每一次簡化后,都要運行一下,保證它的結果如下所示,否則說明我們簡化過程中有錯誤:

@*@**@***@****@*****@******@*******@********@*********@********** ......

在理解continuation之前,這一結果是我們無論如何也想不到的,如果你有足夠奈心的話,你會發現它會按這一規律無限的延長,在我的老機上從11:20一直到15:20仍無跡象表明要停止。

為什么會出現這一結果呢?首先看看我們為了簡化而定義的三個過程bar、foox和fooy,bar是Scheme語言中最簡單的過程,只有一個參數,并且返回這個參數;foox和fooy比它們只多了一步,執行完一個display過程后再返回這個參數;這樣簡單的兩個過程確出現如此復雜的結果,原因全在call/cc身上。

首先,看(call/cc bar)表達式,它形成了一個靈活的continuation,用它來做foox的參數,表達式(foox (call/cc bar))則形成一個新的靈活的continuation,即顯示一個@字符同時也是一個continuation。

再看,表達式((call/cc bar) (foox bar))的結果與表達式((foox bar) (foox bar))的結果相同,均為顯示兩個@字符,同時返回一個過程#<procedure bar (bar)>,這就讓我們在某種程度上理解了表達式(call/cc procedure?)的結果為#t了,因為(procedure? procedure?)也為#t;而(call/cc boolean?)則不然,因為(boolean? boolean?)為#f。

從上面我們可以看出表達式((call/cc bar) (foox (call/cc bar)))實際則是與表達式((foox (call/cc bar)) (foox (call/cc bar))),運行一下會發現,兩者確實相同,顯示@字符,無窮無盡,看來(call/cc bar)這個參數起到了關鍵作用。那么再看我們的陰陽表達式((foox (call/cc bar)) (fooy (call/cc bar))),前者(foox (call/cc bar))為一個顯示字符@的continuation,我們表示為*cc;后者(fooy (call/cc bar))為一個顯示字符*的continuation,我們表示為*cc;則在@cc的調動指揮下,每次*cc再加下一個(fooy (call/cc bar)),形成**cc,再加后,如此形成我們前面的效果。過程如下所示:

@cc *cc @cc **cc @cc ***cc @cc ****cc 一直至無窮盡

前一段時間,"暈"這個字總會出現在網友的聊天中,相信現在不"暈"了。我們給上面的代碼改動一下:

#! /usr/local/bin/guile -s !# (define call/cc call-with-current-continuation) (define n 0) (define bar (lambda (bar) bar)) (define foo (lambda (foo) (display n) (newline) (set! n (+ n 1)) foo)) ((call/cc bar) (foo (call/cc bar)))

這樣形成了,0 1 2 3 4 ......無限制的循環,由此我們解決了一個不用循環語句來形成循環的問題。

五、關于記錄類型

在guile中提供了很多復雜的復合類型,如record,struct,hashtable,array等等,record類型是其中較簡單的一種,我們這里稱之為記錄類型,這種類型有點像C語言中的結構,更像C++中的類。通過它我們可以了解一些面向對象思想在Scheme語言中的應用。記錄類型包括九個相關過程,以下是簡單介紹:

record? 記錄類型的判斷過程
make-record-type 創建記錄類型,兩個參數,類型的名稱和類型的成員名稱列表
record-constructor 創建記錄類型構建過程,一個參數,類型
record-predicate 創建記錄類型的判斷過程,用此過程某一變量是否為已創建的記錄類型
record-accessor 創建記錄類型的get系列過程,兩個參數,類型和表示成員名稱的符號
record-modifier 創建記錄類型的set系列過程,同上
record-type-descriptor 一般不用,可忽略
record-type-name 取得記錄類型的名字,返回字符串
record-type-fields 取得記錄類型的成員名字列表

要想知道如何定義一個記錄類型和上面提到的相關過程的用法,具體代碼是必不可少的,下面是一個簡單的示例:

#! /usr/local/bin/guile -s !# (define girl (make-record-type "girl" '(name info))) ;;定義record類型girl,包含兩個成員name和info,其中name為一字符串,info為一過程用來顯示信息 (define girl-init! (record-constructor girl)) ;;定義girl的初始化過程 (define girl-name-get (record-accessor girl 'name)) ;;定義取得girl類型的name成員的值的過程 (define girl-name-set! (record-modifier girl 'name)) ;;定義設定girl類型的name成員的值的過程 (define girl-info-get (record-accessor girl 'info)) ;;定義取得girl類型的info成員的值的過程 (define girl-info-set! (record-modifier girl 'info)) ;;定義設定girl類型的info成員的值的過程 (define hi(lambda (name)(display "Hi! I'm ")(display name)(display "."))) ;;定義hi過程,顯示"Hi! I'm " 加字符串 name 加 "." (define g (girl-init! "Lucy" hi)) ;;定義一個girl類型的變量g,其成員name值為"Lucy",成員info值為上面定義的hi過程 ((girl-info-get g) (girl-name-get g)) (newline) ;;取得girl類型變量g的info成員,做為過程來執行它,取得girl類型變量g的name成員做為此過程的參數

這段代碼的運行結果為: Hi! I'm Lucy.

代碼中的注釋相信大家都能看懂,需要說的是當我們用定義一個用make-record-type創建的記錄類型后,就可以用record?來判斷此類型是否為記錄類型了,即 (record? girl) ==> #t 。

還有就是可以用代碼 (define girl? (record-predicate girl)) 來定義一個此記錄類型girl的判斷過程girl?,也就是 (girl? g) ==> #t 。

還有就是下面的結果也應該在我們的想象之中:

(record-type-name girl) ==> "girl" (record-type-fields girl) ==> (name info)

從這個簡單的例子來看,記錄類型已經具備了面向對象的編程思想所要求的一些必備的東西,而且更具有Scheme語言自己的特色。相信在我的這個例子基礎上你可以創建一個更優秀的girl來。

六、關于宏定義

Scheme語言中的宏定義類似于自己定義一個Scheme語言關鍵字,可以實現不同的功能,很多關鍵字都可以通過宏定義來實現,我們在多數參考資料中都可以看到這樣的例子。

在多數Scheme語言的實現中,都提供了不同形式的宏定義功能,在guile中提供了用defmacro或define-macro來定義宏,defmacro的格式為:

(defmacro name (args ...) body ...)

它等同于

(define-macro (name args ...) body ...)

我們來看一個簡單的宏定義:

#! /usr/local/bin/guile -s !# (define-macro (defobj name)`(begin(define ,(string->symbol (string-append "make-" name))(lambda ()(display "make object ok!\n")))(define ,(string->symbol (string-append name "?"))(lambda (obj)(if (eq? obj 'name)#t#f))))) (defobj "foo") (make-foo) (display (foo? 'name)) (newline) 這段程序的運行結果如下: make object ok! #t

從這段代碼中你可能看到了逆符號(quasiquote)以及相關的操作的重要性了,這里我們定義了一個宏defobj,當運行完(defobj "boy")這個宏時,產生了兩個過程定義即make-foo和foo?,從這一點上來看,高性能的宏定義可以大大減輕我們代碼的重復使用的麻煩。還有就是guile系統中很多宏定義都是按上面的宏定義方式來進行的。

在Scheme語言中,R5RS中的宏定義是通用的標準,guile中通過調用syncase模塊來實現這一功能,你需要在代碼中加入:(use-modules (ice-9 syncase)) 來調用ice-9目錄下的syncase模塊。

下面的R5RS格式的宏定義實現了前面提到的sum功能和一個列表定義功能,它都有多參數的特點,這在R5RS宏觀定義中很容易實現:

#! /usr/local/bin/guile -s !# (use-modules (ice-9 syncase)) (define-syntax sum(syntax-rules ()((_ exp1 exp2 ...)(+ exp1 exp2 ...)))) (display (sum 1 2 3 4 5)) (newline) (define-syntax ourlst(syntax-rules ()((_ exp)(cons exp '()))((_ exp1 exp2 ...)(cons exp1 (ourlst exp2 ...))))) (display (ourlst 1 2 3 4 5)) (newline)

上面代碼的結果如下:

15 (1 2 3 4 5)

在sum宏定義中,如果附合規則(_ exp1 exp2 ...)或(sum exp1 exp2 ...)將按(+ exp1 exp2 ...)的方式來處理,其中exp1、exp2表示Scheme表達式,而省略號 ... 則表示更多的表達式。也就是說 (sum 1 2 3 4 5)將按(+ 1 2 3 4 5)來處理,其結果為15。在ourlst宏中則有兩個規則,第一是只有一個參數的情況,第二是多參數的情況,在多參數情況下還用到了遞歸,相信大家都能理解。

這是按R5RS標準來實現的最簡單的兩個宏(當然還有我在概要一文中提到的糟糕的start宏),相信通過這兩個宏的定義,您會理解并能進行宏定義了。

七、關于模塊

上面提到的syncase模塊是如何實現的呢?多數Scheme語言的實現版本都提供了一套模塊系統,guile也不例外,看一看下面的簡單的模塊定義:

;;;file : tt.scm , a module test here . (define-module (ice-9 tt):export (newdisplay)) (define newdisplay(lambda (str)(display str)(newline)))

將其以tt.scm文件名保存。這段代碼中,首先用define-module表示定義一個模塊,而(ice-9 tt)則指明了模塊的具體位置和名稱,最后:export指出模塊中要導出的過程等的名稱,這們這里只有一個newdisplay,可以用列表來形成多個導出過程名。而下面的代碼則和我們普通的過程定義代碼相同,簡單的定義了一個名為newdisplay的過程,功能是在要顯示的東西后面加一個換行符。

我們再編寫一段代碼來測試一下這個模塊:

#! /usr/local/bin/guile -s !# (use-modules (ice-9 tt)) (newdisplay "test use tt modules")

這段代碼中用usemodules來調用我們上面定義的tt模塊,以test_tt.scm文件名保存,運行后會出現錯誤信息: ERROR: no code for module (ice-9 tt) 。

這是因為,默認情況下,模塊所在目錄為/usr/local/share/guile/1.6/ice-9 或 /usr/share/guile/1.6/ice-9 。執行命令: cp tt.scm /usr/local/share/guile/1.6/ice-9/ ,將模塊文件復制到相應目錄,再執行test_tt.scm文件,其輸出結果如下:(輸出字符串自動換行了)

test use tt modules

這說明我們成功的定義了一個模塊。我們在宏定義時用的syncase模塊實際上就是在/usr/local/share/guile/1.6/ice-9目錄中的syncase.scm文件,研究一下此目錄中的scm文件會發現很多定義模塊的技巧。

八、關于eval

在概要一文中還沒有提到的是eval這個過程的用法,利用它可以實現用Scheme語言本身來解釋Scheme表達式的功能,這對一個學習編譯系統和Scheme語法功能的實現非常重要。下面是在guile中運行eval過程來解釋Scheme表達式的情況:

guile> (primitive-eval '(+ 2 3)) 5 guile> (eval '(+ 1 2) (interaction-environment)) 3

這里面包含了兩個版本的eval過程,首先是原始的guile內部使用的primitive-eval,它可以直接解釋運行Scheme表達式;第二個是正常的eval過程,它需要兩個參數,一個是要解釋運行的要Scheme表達式,第二個是表達式運行的環境,可以由interaction-environment過程來獲得,如此則eval過程正常運行。

可以想象用C語言來寫C語言編譯器對初學者來說的難度,但掌握Scheme語法和eval的用法后,你會發現用Scheme語言來寫一個Scheme語言解釋器并不是很難,這可能成為你理解編譯原理的重要一步。

我們在感覺到Scheme語言的簡單易用的同時,還應該意識到它是一門富于挑戰意義的語言,相信現在我們能夠真正理解Scheme挑戰意義的所在了吧。(本文涉及到了Scheme語言中大多數的稍復雜一些的內容,還請熱愛Scheme語言的朋友們多多指正。)

注:所有代碼在redhat9.0 guile1.6.4下測試通過


參考資料

  • 《the Scheme Programming Language》 Tahird Edition ,《Scheme編程語言》第三版,在線網址: http://www.scheme.com/tspl3/ 這可能是最權威而又不失簡潔的Scheme語言著作了。

  • 本文中的部分代碼包在這里下載, scm.tar.gz。

  • guile的主頁, http://www.gnu.org/software/guile/ 這里又有新版本的guile要發布了,還有guile的參考文檔,內容相當豐富。

  • http://lonelycactus.com/guilebook/book1.html,一本關于如何使用guile做擴展語言的書,還未完善。

  • http://www.schemer.org,Scheme語言愛好者的大本營。


總結

以上是生活随笔為你收集整理的Scheme语言深入的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。