作ؓLisp 变体QScheme 是一门非常简z的计算语言Q用它的编Eh员可以摆pa本n的复杂性,把注意力集中到更重要的问题上Q从而语言真正成ؓ解决问题的工兗本文分Z?下两部分来介l?scheme 语言?/p>
Scheme 语言是LISP语言的一个方a(或说成变U?Q它诞生?975q的MITQ对于这个有q三十年历史的编E语a来说Q它q没有象C++QjavaQC#? 样受到商业领域的青睐Q在国内更是显ؓ人知。但它在国外的计机教育领域内却是有着q泛应用的,有很多h学的W一门计机语言是Scheme语言?/p>
它是一个小巧而又强大的语aQ作Z个多用途的~程语言Q它可以作ؓ脚本语言使用Q也可以作ؓ应用软g的扩展语a来用,它具有元语言Ҏ,q有很多独到的特?以致于它被称为编E语a中的"皇后"?/p>
下面是洪峰对Scheme语言的编E特色的归纳Q?/p>
- 词法定界QLexical ScopingQ?/li>
- 动态类型(Dynamic TypingQ?/li>
- 良好的可扩展?/li>
- N归QTail RecursiveQ?/li>
- 函数可以作ؓD?/li>
- 支持一的计算q箋
- 传D用(passing-by-valueQ?/li>
- 术q算相对独立
本文的目的是让有~程基础Q那怕是一点点Q的朋友能尽快的掌握Scheme语言的语法规则,如果您在d本文后,发现自己已经会用Scheme语言了,那么我的目的pC?/p>
Scheme语言的语法规则的W?ơ修正稿Q?998q制定,即Scheme语言的现行标准,目前大多数Scheme语言的实现都达到或遵@此标准,q且几乎都加入了一些属于自q扩展特色?/p>
Guile是GNU工程的一个项目,它是GNU扩展语言库,它也是Scheme语言的一个具体实玎ͼ如果你将它作Z个库打包Q可以把它链接到你的应用E序中去Q你的应用E序h自己的脚本语aQ这个脚本语a目前是Scheme语言?/p>
Guile可以在LINUX和一些UNIXpȝ上运行,下面是简单的安装q程Q?/p>
下蝲guile-1.6.4版,文g名ؓguile-1.6.4.tar.gzQ执行下面的命oQ?/p>
tar xvfz guile-1.6.4.tar.gz
cd guile-1.6.4
./configure
make
make install
如此Q即可以执行命oguileQ进入guile>提示W状态,输入调试SchemeE序代码了,本文的所有代码都是在guile下调试通过?/p>
除了Guile外,Scheme语言的实现还有很多,如:GNU/MIT-SchemeQ? SCIQScheme48QDrScheme{,它们大多是开源的Q可以自׃载安装用,q且跨^台的实现也很多。你会发现既有象basic? Scheme语言解释器,也有Scheme语言~译成C语言的编译器Q也有象JAVA那样Scheme语言代码~译成虚拟机代码的编译器?/p>
Scheme语言中的注释是单行注释,以分号[;]开始一直到行尾l束Q其中间的内容ؓ注释Q在E序q行时不做处理,如:
; this is a scheme comment line.
标准的Scheme语言定义中没有多行注释,不过在它的实C几乎都有。在Guile中就有多行注释,以符L?#!"开始,以相反的另一W号l合"!#"l束Q其中内容ؓ注释Q如Q?/p>
#!
there are scheme comment area.
you can write mulity lines here .
!#
注意的是Q符L?#!"?!#"一定分做两行来写?/p>
Scheme语言可以象shQperlQpython{语a那样作ؓ一U脚本语a来用,用它来编写可执行脚本Q在Linux中如果通过Guile用Scheme语言写可执行脚本Q它的第一行和W二行一般是cM下面的内容:
#! /usr/local/bin/guile -s
!#
q样的话代码在运行时会自动调用Guile来解释执行,标准的文件尾~?.scm"?/p>
?form)是Scheme语言中的最程序单元,一个Scheme语言E序是由一个或多个form构成。没有特D说明的情况?form 都由括hhQŞ如:
(define x 123)
(+ 1 2)
(* 4 5 6)
(display "hello world")
一?form 也可以是一个表辑ּQ一个变量定义,也可以是一个过E?/p>
Scheme语言中允许form的嵌套,q它可以轻杄实现复杂的表辑ּQ同时也是一U非常有自己特色的表辑ּ。下囄意了嵌套的稍复杂一点的表达式的q算q程Q?/p>
可以用define来定义一个变量,形式如下Q?/p>
(define 变量??
如: (define x 123) Q定义一个变量xQ其gؓ123?/p>
更改变量的?/p>
可以用set!来改变变量的|格式如下Q?/p>
(set! 变量??
如: (set! x "hello") Q将变量x的值改?hello" ?/p>
Scheme语言是一U高U语aQ和很多高语言(如pythonQperl)一P它的变量cd不是固定的,可以随时改变?/p>
最基本的数据类型,也是很多计算a中都支持的最单的数据cdQ只能取两个|#tQ相当于其它计算a中的 TRUEQ?fQ相当于其它计算a中的 FALSE?/p>
Scheme语言中的booleancd只有一U操作:not。其意ؓ取相反的|卻I
(not #f) => #t
(not #t) => #f
not的引用,与逻辑非运操作类?/p>
guile> (not 1)
#f
guile> (not (list 1 2 3))
#f
guile> (not 'a)
#f
从上面的操作中可以看出来Q只要not后面的参C是逻辑型,其返回值均?f?/p>
它又分ؓ四种子类型:整型(integer)Q有理数?rational)Q实?real)Q复数型(complex)Q它们又被统一UCؓ数字cd(number)?/p>
如:复数?complex) 可以定义?(define c 3+2i) 实数型(realQ可以定义ؓ (define f 22/7) 有理数型QrationalQ可以定义ؓ (define p 3.1415) 整数?integer) 可以定义?(define i 123)
Scheme 语言中,数字cd的数据还可以按照q制分类Q即二进Ӟ八进Ӟ十进制和十六q制Q在外观形式上它们分别以W号l合 #b? #o?#d?#x 来作C数字进制类型的前缀Q其中表C十q制?d可以省略不写Q如Q二q制?#b1010 Q八q制? #o567Q十q制?23?#d123Q十六进制的 #x1afc ?/p>
Scheme语言的这U严格按照数学定理来为数字类型进行分cȝҎ可以看出Scheme语言里面渗透着很深的数学思想QScheme语言是由数学家们创造出来的Q在q方面表现得也比较鲜明?/p>
Scheme语言中的字符型数据均以符L?"#\" 开始,表示单个字符Q可以是字母、数字或"[ ! $ % & * + - . / : < = > ? @ ^ _ ~ ]"{等其它字符Q如Q? #\A 表示大写字母AQ?\0表示字符0Q? 其中Ҏ字符有:#\space 表示I格W和 #\newline 表示换行W?/p>
W号cd是Scheme语言中有多种用途的W号名称Q它可以是单词,用括hh的多个单词,也可以是无意义的字母l合或符L合,它在某种意义上可以理解ؓC中的枚Dcd。看下面的操作:
guile> (define a (quote xyz)) ; 定义变量a为符L型,gؓxyz
guile> a
xyz
guile> (define xyz 'a) ; 定义变量xyz为符L型,gؓa
guile> xyz
a
此处也说明单引号' 与quote是等LQƈ且更单一些。符L型与字符串不同的是符L型不能象字符串那样可以取得长度或改变其中某一成员字符的|但二者之间可以互相{换?/p>
可以说复合数据类型是由基本的单数据类型通过某种方式加以l合形成的数据类型,特点是可以容U_U或多个单一的简单数据类型的数据Q多数是Z某一U数学模型创建的?/p>
字符?string) 由多个字W组成的数据cdQ可以直接写成由双引hL内容Q如Q?hello" 。下面是Guile中的字符串定义和相关操作Q?/p>
guile> (define name "tomson")
guile> name
"tomson"
guile> (string-length name) ; 取字W串的长?br>6
guile> (string-set! name 0 #\g) ; 更改字符串首字母(W?个字W?为小写字母g (#\g)
guile> name
"gomson"
guile> (string-ref name 3) ; 取得字符串左侧第3个字W(?开始)
#\s
字符串还可以用下面的形式定义Q?/p>
guile> (define other (string #\h #\e #\l #\l #\o ))
guile> other
"hello"
字符串中出现引号时用反斜U加引号代替Q如Q?abc\"def" ?/p>
我把它译?点对"Q它是一U非常有的cdQ也是一些其它类型的基础cdQ它是由一个点和被它分隔开的两个所值组成的。Ş如: (1 . 2) ?(a . b) Q注意的是点的两ҎI格?/p>
q是最单的复合数据cdQ同是它也是其它复合数据cd的基cdQ如列表cdQlistQ就是由它来实现的?/p>
按照Scheme语言说明中的惯例Q以下我们用W号l合 "=>" 来表C辑ּ的倹{?/p>
它用cons来定义,如: (cons 8 9) =>(8 . 9)
其中在点前面的DUCؓ car Q在点后面的DUCؓ cdr Qcar和cdr同时又成为取pair的这两个值的q程Q如Q?/p>
(define p (cons 4 5)) => (4 . 5)
(car p) => 4
(cdr p) => 5
q可以用set-car! ?set-cdr! 来分别设定这两个|
(set-car! p "hello")
(set-cdr! p "good")
如此Q以前定义的 p 又变成了 ("hello" . "good") q个样子了?/p>
列表是由多个相同或不同的数据q箋l成的数据类型,它是~程中最常用的复合数据类型之一Q很多过E操作都与它相关。下面是在Guile中列表的定义和相x作:
guile> (define la (list 1 2 3 4 ))
guile> la
(1 2 3 4)
guile> (length la) ; 取得列表的长?br>4
guile> (list-ref la 3) ; 取得列表W?的|?开始)
4
guile> (list-set! la 2 99) ; 讑֮列表W?的gؓ99
99
guile> la
(1 2 99 4)
guile> (define y (make-list 5 6)) ;创徏列表
guile> y
(6 6 6 6 6)
make-list用来创徏列表Q第一个参数是列表的长度,W二个参数是列表中添充的内容Q还可以实现多重列表Q即列表的元素也是列表,如:(list (list 1 2 3) (list 4 5 6))?/p>
回过头来Q我们再看看下面的定义:
guile> (define a (cons 1 (cons 2 (cons 3 '()))))
guile> a
(1 2 3)
׃可见Qa本来是我们上面定义的点对Q最后Ş成的却是列表。事实上列表是在点对的基上Ş成的一U特D格式?/p>
再看下面的代码:
guile> (define ls (list 1 2 3 4))
guile> ls
(1 2 3 4)
guile> (list? ls)
#t
guile> (pair? ls)
#t
由此可见Qlist是pair的子cdQlist一定是一个pairQ而pair不是list?/p>
guile> (car ls)
1
guile> (cdr ls)
(2 3 4)
其cdr又是一个列表,可见用于pair的操作过E大多可以用于list?/p>
guile> (cadr ls) ; ?点对"对象的cdr的car
2
guile> (cddr ls) ; ?点对"对象的cdr的cdr
(3 4)
guile> (caddr ls) ; ?点对"对象的cdr的cdr的car
3
guile> (cdddr ls) ; ?点对"对象的cdr的cdr的cdr
(4)
上在的操作中用到的cadrQcdddr{过E是专门对PAIR型数据再复合形成的数据操作的q程Q最多可以支持在中间加四位a或dQ如cdddrQcaaddr{?/p>
下图表示了由pairs定义形成的列表:
q个列表可以由pair定义为如下Ş式:
(define x (cons 'a (cons 'b (cons 'c (cons 'd '())))))
而列表的实际内容则ؓQ?a b c d)
由paircdq可以看出它可以L的表C树型结构,其是标准的二叉树?/p>
可以说是一个非常好用的cd Q是一U元素按整数来烦引的对象Q异源的数据l构Q在占用I间上比同样元素的列表要,在外观上Q?/p>
列表CZؓQ?(1 2 3 4) VECTOR表示为: #(1 2 3 4) 可以正常定义Q?define v (vector 3 4 5)) 也可以直接定义:(define v #(3 4 5))
vector是一U比较常用的复合cdQ它的元素烦引从0开始,至第 n-1 l束Q这一Ҏ点类似C语言中的数组?/p>
关于向量表(vectorQ的常用操作q程Q?/p>
guile> (define v (vector 1 2 3 4 5))
guile> v
#(1 2 3 4 5)
guile> (vector-ref v 0) ; 求第n个变量的?br>1
guile> (vector-length v) ; 求vector的长?br>5
guile> (vector-set! v 2 "abc") ; 讑֮vectorWn个元素的?br>guile> v
#(1 2 "abc" 4 5)
guile> (define x (make-vector 5 6)) ; 创徏向量?br>guile> x
#(6 6 6 6 6)
make-vector用来创徏一个向量表Q第一个参数是数量Q后一个参数是d的|q和列表中的make-list非常怼?/p>
我们可以看出Q在Scheme语言中,每种数据cd都有一些基本的和它相关的操作过E,如字W串Q列表等相关的操作,q些操作q程都很有规律,q程名的单词之间都用-号隔开Q很Ҏ理解。对于学qC++的朋友来_更类g某个对象的方法,只不q表现的形式不同了?/p>
Scheme语言中所有判断都是用cd名加问号再加相应的常量或变量构成QŞ如:
(cd? 变量)
Scheme语言在类型定义中有比较严格的界定Q如在C语言{一些语a中数?来代曉K辑cd数据FalseQ在Scheme语言中是不允许的?/p>
以下为常见的cd判断和附加说明:
逻辑型:
(boolean? #t) => #t
(boolean? #f) => #t 因ؓ#t?f都是booleancdQ所以其gؓ#t
(boolean? 2) => #f 因ؓ2是数字类型,所以其gؓ #f
字符型:
(char? #\space) => #t
(char? #\newline) => #t 以上两个Ҏ字符Q空格和换行
(char? #\f) => #t 写字母 f
(char? #\;) => #t 分号 ;
(char? #\5) => #t 字符 5 Q以上这些都是正的Q所以返回值都?#t
(char? 5) => #f q是数字 5 Q不是字W类型,所以返?#f
数字型:
(integer? 1) => #t
(integer? 2345) => #t
(integer? -90) => #t 以上三个数均为整?br>(integer? 8.9) => #f 8.9不整?br>(rational? 22/7) => #t
(rational? 2.3) => #t
(real? 1.2) => #t
(real? 3.14159) => #t
(real? -198.34) => #t 以上三个数均为实数型
(real? 23) => #t 因ؓ整型属于实型
(number? 5) => #t
(number? 2.345) => #t
(number? 22/7) => #t
其它型:
(null? '()) => #t ; null意ؓI类型,它表CZؓ '() Q即括号里什么都没有的符?br>(null? 5) => #f
(define x 123) 定义变量x其gؓ123
(symbol? x) => #f
(symbol? 'x) => #t ; 此时 'x 为符号xQƈ不表C变量x的?br>
在Scheme语言中如此众多的cd判断功能Q得Scheme语言有着非常好的自省功能。即在判断过E的参数是否附合q程的要求?/p>
Scheme语言中可以用<Q?gt;Q?lt;=Q?gt;=Q? 来判断数字类型值或表达式的关系Q如判断变量x是否{于Ӟ它的形式是这LQ?= x 0) Q如x的gؓ0则表辑ּ的gؓ#tQ否则ؓ#f?/p>
q有下面的操作:
(eqv? 34 34) => #t
(= 34 34) => #t
以上两个form功能相同Q说?eqv? 也可以用于数字的判断?/p>
在Scheme语言中有三种相等的定义,两个变量正好是同一个对象;两个对象h相同的|两个对象h相同的结构ƈ且结构中的内容相同。除了上面提到的W号判断q程和eqv?外,q有eq?和equal?也是判断是否相等的过E?/p>
eq?Qeqv?和equal?是三个判断两个参数是否相{的q程Q其中eq?和eqv?的功能基本是相同的,只在不同的Scheme语言中表C一栗?/p>
eq?是判断两个参数是否指向同一个对象,如果是才q回#tQequal?则是判断两个对象是否h相同的结构ƈ且结构中的内Ҏ否相同,它用eq?来比较结构中成员的数量;equal?多用来判断点对,列表Q向量表Q字W串{复合结构数据类型?/p>
guile> (define v (vector 3 4 5))
guile> (define w #(3 4 5)) ; w和v都是vectorcdQ具有相同的?(3 4 5)
guile> (eq? v w)
#f ; 此时w和v是两个对?br>guile> (equal? v w)
#t ; W合equal?的判断要?br>
以上操作说明了eq? 和equal? 的不同之处,下面的操作更是证明了q一点:
guile> (define x (make-vector 5 6))
guile> x
#(6 6 6 6 6)
guile> (eq? x x) ; 是同一个对象,所以返?t
#t
guile> (define z (make-vector 5 6))
guile> z
#(6 6 6 6 6)
guile> (eq? x z) ; 不是同一个对?br>#f
guile> (equal? x z) ; l构相同Q内容相同,所以返?t
#t
Scheme语言中的q算W有Q? + , - , * , / ?expt (指数q算) 其中 - ?/ q可以用于单目运,如:
(- 4) => -4
(/ 4) => 1/4
此外q有许多扩展的库提供了很多有用的q程Q?/p>
max 求最?(max 8 89 90 213) => 213
min 求最?(min 3 4 5 6 7) => 3
abs 求绝对?(abs -7) ==> 7
除了maxQminQabs外,q有很多数学q算q程Q这要根据你用的Scheme语言的运行环境有养I不过它们大多是相同的。在R5RS中规定了很多q算q程Q在R5RS的参考资料中可以很容易找到?/p>
Scheme语言中用W号l合"->"来标明类型间的{换(很象C语言中的指针Q的q程Q就象用问号来标明类型判断过E一栗下面是一些常见的cd转换q程Q?/p>
guile> (number->string 123) ; 数字转换为字W串
"123"
guile> (string->number "456") ; 字符串{换ؓ数字
456
guile> (char->integer #\a) ;字符转换为整型数Q小写字母a的ASCII码gؓ96
97
guile> (char->integer #\A) ;大写字母A的gؓ65
65
guile> (integer->char 97) ;整型数{换ؓ字符
#\a
guile> (string->list "hello") ;字符串{换ؓ列表
(#\h #\e #\l #\l #\o)
guile> (list->string (make-list 4 #\a)) ; 列表转换为字W串
"aaaa"
guile> (string->symbol "good") ;字符串{换ؓW号cd
good
guile> (symbol->string 'better) ;W号cd转换为字W串
"better"
在Scheme语言中,q程相当于C语言中的函数Q不同的是Scheme语言q程是一U数据类型,q也是ؓ什么Scheme语言程序和数据作ؓ同一对象处理的原因。如果我们在Guile提示W下输入加号然后回RQ会出现下面的情况:
guile> +
#<primitive-procedure +>
q告诉我?+"是一个过E,而且是一个原始的q程Q即Scheme语言中最基础的过E,在GUILE中内部已l实现的q程Q这和类型判断一P? boolean?{,它们都是Scheme语言中最基本的定义。注意:不同的Scheme语言实现环境Q出现的提示信息可能不尽相同Q但意义是一L?/p>
define不仅可以定义变量Q还可以定义q程Q因在Scheme语言中过E(或函敎ͼ都是一U数据类型,所以都可以通过define来定义。不同的是标准的q程定义要用lambdaq一关键字来标识?/p>
Scheme语言中可以用lambda来定义过E,其格式如下: (define q程?( lambda (参数 ...) (操作q程 ...)))
我们可以自定义一个简单的q程Q如下:
此过E需要一个参敎ͼ其功能ؓq回此参数加5 的|如:
(add5 11) => 16
下面是简单的求^方过Esquare的定义:
(define square (lambda (x) (* x x)))
在Scheme语言中,也可以不用lambdaQ而直接用define来定义过E,它的格式为: (define (q程?参数) (q程内容 …))
如下面操作:
(define (add6 x) (+ x 6))
add6
#<procedure: add6 (x)> 说明add6是一个过E,它有一个参数x
(add6 23) => 29
再看下面的操作:
guile> (define fun
(lambda(proc x y)
(proc x y)))
guile> fun
#<procedure fun (proc x y)>
guile> (fun * 5 6)
30
guile> (fun / 30 3)
10
上面定义的过Efun有三个参敎ͼ其中W一个参数proc也是一个操作过E(因ؓ在Scheme语言中过E也是一U数据,可以作ؓq程的参敎ͼQ另外两个参数是数|所以会出现上面的调用结果?/p>
guile> (define add
(lambda (x y)
(+ x y)))
guile> add
#<procedure add (x y)>
guile> (fun add 100 200)
300
l箋上面操作Q我们定义一个过EaddQ将add作ؓ参数传递给funq程Q得出和(fun + 100 200)相同的结果?/p>
guile> ((lambda (x) (+ x x)) 5)
10
上面?(lambda(x) (+ x x)) 事实上是单的q程定义Q在后面直接加上操作参数5Q得出结?0Q这样实C匿名q程Q直接用q程定义来操作参敎ͼ得出q算l果?/p>
通过上面的操作,怿你已初步了解了过E的用法。既然过E是一U数据类型,所以将q程作ؓq程的参数是完全可以的。以下过Eؓ判断参数是否E,l出一个参敎ͼ?procedure? 来判断参数是否ؓq程Q采用ifl构Q关于ifl构见下面的介绍Q:
guile> (define isp
(lambda (x)
(if (procedure? x) 'isaprocedure 'notaprocedure)))
guile> isp
#<procedure isp (x)>
guile> (isp 0)
notaprocedure
guile> (isp +)
isaprocedure
上面的过E就体现了Scheme语言的参数自省(辨别Q能力,'0'是数字型Q所以返回notaprocedureQ?+'是一个最基础的操作过E,所以返回isaprocedure?/p>
在Scheme语言中,q程定义也可以嵌套,一般情况下Q过E的内部q程定义只有在过E内部才有效Q相当C语言中的局部变量?/p>
如下面的代码的最l结果是50Q?/p>
(define fix
(lambda (x y z)
(define add
(lambda (a b) (+ a b)))
(- x (add y z))))
(display (fix 100 20 30))
此时q程add只在fixq程内部起做用,q事实上涉及了过E和变量的绑定,可以参考下面的关于q程l定QletQlet* 和letrecQ的介绍?/p>
q程是初学者难理解的一个关键,随着q程参数的增加和功能的增强,q程的内容变得越来越复杂Q小括号也会更多Q如果不写出清晰的代码的话,M码也会成Z个难题?/p>
熟悉?scheme 基本概念、数据类型和q程Q函敎ͼ后, 下一部分我们来学?scheme 的结构、递归调用和其他扩展功能?/p>
也可以说成由多个forml成的formQ用begin来将多个form攑֜一对小括号内,最lŞ成一个form。格式ؓQ?begin form1 form2 …)
如用Scheme语言写成的经典的helloworldE序是如下样子的Q?/p>
(begin
(display "Hello world!") ; 输出"Hello world!"
(newline)) ; 换行
Scheme语言的ifl构有两U格式,一U格式ؓQ?if 试 q程1 q程2)Q即试条g成立则执行过E?Q否则执行过E?。例如下面代码:
(if (= x 0)
(display "is zero")
(display "not zero"))
q有另一U格式:(if 试 q程) Q即试条g成立则执行过E。例如下面代码:
(if (< x 100) (display "lower than 100"))
Ҏcd判断来实现自省功能,下面代码判断l定的参数是否ؓ字符Ԍ
(define fun
(lambda ( x )
(if (string? x)
(display "is a string")
(display "not a string"))))
如执?(fun 123) 则返回gؓ"not a string"Q这L功能在C++或JAVA中实现的话可能会很费力气?/p>
Scheme语言中的condl构cM于C语言中的switchl构Qcond的格式ؓQ?/p>
(cond ((试) 操作) … (else 操作))
如下是在Guile中的操作Q?/p>
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
上面E序代码中,我们定义了过EwQ它有一个参数xQ如果x的值大?Q则q回W号upperQ如x的值小?则返回符号lowerQ如x 的gؓ0则返回符号equal?/p>
下蝲已做成可执行脚本?例程?/p>
cond可以用if形式来写Q上面的q程可以如下定义Q?/p>
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
q在功能上是和cond一LQ可以看出cond实际上是实现了if的一U多重嵌套?/p>
casel构和condl构有点cMQ它的格式ؓQ?/p>
(case (表达? ((? 操作)) ... (else 操作)))
casel构中的值可以是复合cd数据Q如列表Q向量表{,只要列表中含有表辑ּ的这个结果,则进行相应的操作Q如下面的代码:
(case (* 2 3)
((2 3 5 7) 'prime)
((1 4 6 8 9) 'composite))
上面的例子返回结果是compositeQ因为列?1 4 6 8 9)中含有表辑ּ(* 2 3)的结?Q下面是在Guile中定义的funcq程Q用Ccasel构Q?/p>
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.scmQ参考一下?/p>
andl构与逻辑与运操作类|and后可以有多个参数Q只有它后面的参数的表达式的值都?tӞ它的q回值才?tQ否则ؓ#f。看下面的操作:
guile> (and (boolean? #f) (< 8 12))
#t
guile> (and (boolean? 2) (< 8 12))
#f
guile> (and (boolean? 2) (> 8 12))
#f
如果表达式的值都不是boolean型的话,q回最后一个表辑ּ的|如下面的操作Q?/p>
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)
a
orl构与逻辑或运操作类|or后可以有多个参数Q只要其中有一个参数的表达式gؓ#tQ其l果׃ؓ#tQ只有全?f时其l果才ؓ#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
我们q可以用and和orl构来实现较复杂的判断表辑ּQ如在C语言中的表达式:
((x > 100) && (y < 100)) ?((x > 100) || (y > 100))
在Scheme中可以表CZؓQ?/p>
guile> (define x 123)
guile> (define y 80)
guile> (and (> x 100) (< y 100))
#t
guile> (or (> x 100) (> y 100))
#t
Scheme语言中只有ifl构是系l原始提供的Q其它的condQcaseQandQorQ另外还有doQwhenQunless{都是可以用宏定义的方式来定义的Q这一点充分体CScheme的元语言Ҏ,关于doQwhen{结构的使用可以参考R5RS?/p>
在Scheme语言中,递归是一个非帔R要的概念Q可以编写简单的代码很轻杄实现递归调用Q如下面的阶乘过E定义:
(define factoral (lambda (x)
(if (<= x 1) 1
(* x (factoral (- x 1))))))
我们可以下面的调用(factoral 4)Q即4的阶乘的q算q程囄如下Q?/p>
以下为factoralq程在Guile中的q行情况Q?/p>
guile> (define factoral (lambda (x) (if (<= x 1) 1 (* x (factoral (- x 1))))))
guile> factoral
#<procedure factoral (x)>
guile> (factoral 4)
24
下面是一另一U递归方式的定义:
(define (factoral n)
(define (iter product counter)
(if (> counter n)
product
(iter (* counter product) (+ counter 1))))
(iter 1 1))
(display (factoral 4))
q个定义的功能和上面的完全相同,只是实现的方法不一样了Q我们在q程内部实现了一个过EiterQ它用counter参数来计敎ͼ调用时从1开始篏计,q样它的展开q程正好和我们上面的递归q程的从4?相反Q而是???/p>
在Scheme语言中没有@环结构,不过循环l构可以用递归来很L的实玎ͼ在Scheme语言中只有通过递归才能实现循环Q。对于用惯了C语言循环的朋友,在Scheme中可以用递归单实玎ͼ
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
q只是一U简单的循环定义Q过E有两个参数Q第一个参数是循环的初始|W二个参数是循环l止|每次增加1。相信读者朋友一定会写出更漂亮更实用的@环操作来的?/p>
在多数编E语a中都有关于变量的存在的时限问题,Scheme语言中用letQlet*和letrec来确定变量的存在的时限问题,卛_部变量和全局变量Q一般情况下Q全局变量都用define来定义,q放在过E代码的外部Q而局部变量则用let{绑定到q程内部使用?/p>
用let可以变量或q程l定在过E的内部Q即实现局部变量:
guile> let
#<primitive-macro! let>
从上面的操作可以看出let是一个原始的宏,即guile内部已经实现的宏定义?/p>
下面的代码显CZlet的用法(注意多了一层括PQ?/p>
guile> (let ((x 2) (y 5)) (* x y))
10
它的格式是:(let ((…)…) …)Q下面是E复杂的用法Q?/p>
guile> (let ((x 5))
(define foo (lambda (y) (bar x y)))
(define bar (lambda (a b) (+ (* a b) a)))
(foo (+ x 3)))
45
以上是Guile中的代码实现情况。它的实现过E大致是Q?foo 8) 展开后Ş?(bar 5 8)Q再展开后Ş?(+ (* 5 8) 5) Q最后其gؓ45?/p>
再看下面的操作:
guile> (let ((iszero?
(lambda(x)
(if (= x 0) #t #f))))
(iszero? 9))
#f
guile> (iszero? 0) ;此时会显C出错信?br>
let的绑定在q程内有效,q程外则无效Q这和上面提到的q程的嵌套定是一LQ上面的iszero?q程在操作过E内定义q用的Q操作结束后再另行引用则无效Q显CE未定义出错信息?/p>
下面操作演示了let*的用法:
guile> (let ((x 2) (y 5))
(let* ((x 6)(z (+ x y))) ;此时x的值已?Q所以z的值应?1Q如此最后的gؓ66
(* z x)))
66
q有letrecQ看下面的操作过E:
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
上面的操作过E中Q内部定义了两个判断q程even?和odd?Q这两个q程是互盔R归引用的,如果letrec换成let或let*都会不正常,因ؓletrec是将内部定义的过E或变量间进行相互引用的。看下面的操作:
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,listoff
letrec帮助局部过E实现递归的操作,q不仅在letrecl定的过E内Q而且q包括所有初始化的东西,q得在~写较复杂的q程中经常用到letrecQ也成了理解它的一个难炏V?/p>
apply的功能是为数据赋予某一操作q程Q它的第一个参数必需是一个过E,随后的其它参数必需是列表,如:
guile> (apply + (list 2 3 4))
9
guile> (define sum
(lambda (x )
(apply + x))) ; 定义求和q程
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)))) ; 定义求^均过E?br>guile> avg
#<procedure avg (x)>
guile> (avg ls)
4
以上定义了求和过Esum和求q_的过EavgQ其中求和的q程sum中用Capply来绑?+"q程操作到列表,l果q回列表中所有数的d?/p>
map的功能和apply有些怼Q它的第一个参C必需是一个过E,随后的参数必需是多个列表,q回的结果是此过E来操作列表后的|如下面的操作Q?/p>
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)
除了applyQmap以外QScheme语言中还有很多,诸如QevalQdelayQfor-eachQforceQcall-with- current- continuation{过E绑定的操作定义Q它们都无一例外的提供了相当灉|的数据处理能力,也就是另初学者望而生畏的法Q当你仔l的体会了运过 E中用到的简直妙不可a的算法后Q你׃发现Scheme语言设计者的思想是多么伟大?/p>
Scheme语言中也提供了相应的输入输出功能Q是在C基础上的一U封装?/p>
Scheme语言中输入输Z用到了端口的概念Q相当于C中的文g指针Q也是Linux中的讑֤文gQ请看下面的操作Q?/p>
guile> (current-input-port)
#<input: standard input /dev/pts/0> ;当前的输入端?br>guile> (current-output-port)
#<output: standard output /dev/pts/0> ;当前的输出端?br>
判断是否入输出端口,可以用下面两个过E:input-port? 和output-port? Q其中input-port?用来判断是否入端口,output-port?用来判断是否出端口?/p>
open-input-fileQopen-output-fileQclose-input-portQclose-output-portq四个过E用来打开和关闭输入输出文Ӟ其中打开文g的参数是文g名字W串Q关闭文件的参数是打开的端口?/p>
打开一个输入文件后Q返回的是输入端口,可以用readq程来输入文件的内容Q?/p>
guile> (define port (open-input-file "readme"))
guile> port
#<input: readme 4>
guile> (read port)
GUILE语言
上面的操作打开了readme文gQƈd了它的第一行内宏V此外还可以直接用readq程来接攉盘输入,如下面的操作Q?/p>
guile> (read) ; 执行后即{待键盘输入
12345
12345
guile> (define x (read)) ; {待键盘输入q赋值给x
12345
guile> x
12345
以上为用read来读取键入的数字Q还可以输入字符串等其它cd数据Q?/p>
guile> (define name (read))
tomson
guile> name
tomson
guile> (string? name)
#f
guile> (symbol? name)
#t
此时输入的tomson是一个符L型,因ؓ字符串是用引号引h的,所以出C面的情况。下面因为用引号了,所?string? str)q回gؓ#t ?/p>
guile> (define str (read))
"Johnson"
guile> str
"Johnson"
guile> (string? str)
#t
q可以用loadq程来直接调用Scheme语言源文件ƈ执行它,格式为:(load "filename")Q还有read-charq程来读单个字符{等?/p>
常用的输E是displayQ还有writeQ它的格式是Q?write 对象 端口)Q这里的对象是指字符串等帔R或变量,端口是指输出端口或打开的文件。下面的操作q程演示了向输出文gtemp中写入字W串"helloworld"Qƈ分行的实现?/p>
[root@toymouse test]# guile
guile> (define port1 (open-output-file "temp")) ; 打开文g端口赋于port1
guile> port1
#<output: temp 3>
guile> (output-port? port1)
#t ; 此时证明port1出端?br>guile> (write "hello\\nworld" port1)
guile> (close-output-port port1)
guile> (exit) ; 写入数据q关闭退?br>[root@toymouse test]# more temp 昄文g的内容,辑ֈ试目的
"hello
world"
在输入输出操作方面,q有很多相关操作Q读者可以参考R5RS的文档?/p>
Scheme语言可以自己定义象condQlet{功能一L宏关键字。标准的Scheme语言定义中用define-syntax和syntax-rules来定义,它的格式如下Q?/p>
(define-syntax 宏名
(syntax-rules()
((模板) 操作))
. . . ))
下面定义的宏start的功能和begin相同Q可以用它来开始多个块的组合:
(define-syntax start
(syntax-rules ()
((start exp1)
exp1)
((start exp1 exp2 ...)
(let ((temp exp1)) (start exp2 ...))) ))
q是一个比较简单的宏定义,但对理解宏定义来说是比较重要的,理解了他你才会进一步应用宏定义。在规则 ((start exp1) exp1) 中,(start exp1) 是一个参数时的模板,exp1是如何处理,也就是原h出,不做处理。这?(start form1) ? (form1) 的功能就相同了?/p>
在规?((start exp1 exp2 ...) (let ((temp exp1)) (start exp2 ...))) 中,(start exp1 exp2 …) 是多个参数时的模板,首先用let来绑定局部变量temp为exp1Q然后用递归实现处理多个参数Q注意这里说的是宏定义中的递归Qƈ不是q程调用中的? 归。另外在宏定义中可以用省略号Q三个点Q来代表多个参数?/p>
在Scheme的规范当中,表辑ּ分ؓ原始表达式和有源表达式,Scheme语言的标准定义中只有原始的if分支l构Q其它均为有源型Q即是用后来的宏定义成的Q由此可见宏定义的重要性。附上面的定义在GUILE中实现的 代码?/p>
在R5RS 中ƈ未对如何~写模块q行说明Q在诸多的Scheme语言的实现当中,几乎无一例外的实C模块的加载功能。所谓模块,实际是一些变量、宏定义和已命名 的过E的集合Q多数情况下它都l定在一个Scheme语言的符号下Q也是名称Q。在Guile中提供了基础的ice-9模块Q其中包括POSIXpȝ? 用和|络操作、正则表辑ּ、线E支持等{众多功能,此外q有著名的SFRI模块。引用模块用use-modulesq程Q它后面的参数指定了模块名和我们 要调用的功能名,如:(use-modules (ice-9 popen))Q如此后Q就可以应用popenq一pȝ调用了。如果你惌定义自己的模块,最好看看ice-9目录中的那些tcm文gQ它们是最原始的定 义?/p>
另外Guile在面向对象编E方面,开发了GOOPSQGuile Object-Oriented Programming SystemQ,对于喜欢OO朋友可以研究一下它Q从中可能会有新的发现?/p>
如何~写输出漂亮的Scheme语言代码应该是初学者的W一个问题,q在Guile中可以用ice-9扩展包中提供的pretty-printq程来实玎ͼ看下面的操作Q?/p>
guile> (use-modules (ice-9 pretty-print)) ; 引用漂亮输出模块
guile> (pretty-print '(define fix (lambda (n)
(cond ((= n 0) 'iszero)
((< n 0) 'lower)
(else 'upper))))) ; 此处是我们输入的不规则代?br>(define fix
(lambda (n)
(cond ((= n 0) 'iszero)
((< n 0) 'lower)
(else 'upper)))) ; 输出的规则代?br>
在把Scheme用做shell语言Ӟl常用到命o行参数的处理Q下面是关于命o行参数的一U处理方法:
#! /usr/local/bin/guile -s
!#
(define cmm (command-line))
(display "应用E序名称Q?)
(display (car cmm))
(newline)
(define args (cdr cmm))
(define long (length args))
(define loop (lambda (count len obj)
(if (<= count len)
(begin
(display "参数 ")
(display count)
(display " 是:")
(display (list-ref obj (- count 1)))
(newline)
(set! count (+ count 1))
(loop count len obj)))))
(loop 1 long args)
下面是运行后的输出结果:
[root@toymouse doc]# ./tz.scm abc 123 ghi
应用E序名称Q?/tz.scm
参数 1 是:abc
参数 2 是:123
参数 3 是:ghi
其中最主要的是用到了command-lineq程Q它的返回结果是命o参数的列表,列表的第一个成员是E序名称Q其后ؓ我们要的参数Q定义loop递归调用形成d数的循环Q显C出参数|辑ֈ我们要的l果?/p>
一些精的自己计算自己的符?/p>
数字 Numbers 2 ==> 2
字符? Strings "hello" ==> "hello"
字符 Charactors #\\g ==> #\\g
辑? Booleans #t ==> #t
量表 Vectors #(a 2 5/2) ==> #(a 2 5/2)
通过变量计算来求值的W号
如:
x ==> 9
-list ==> ("tom" "bob" "jim")
factoral ==> #<procedure: factoral>
==> #<primitive: +>
(define x 9) Qdefine不是一个过E, 它是一个不用求所有参数值的Ҏ的formQ它的操作步骤是Q初始化I间Q绑定符号x到此I间Q然后初始此变量?/p>
下面的这些定义、过E和宏等是必记住的Q?/p>
defineQlambdaQletQletsQletrecQquoteQset!QifQcaseQcondQbeginQandQor{等Q当然还有其它宏Q必需学习Q还有一些未介绍Q可参考有兌料?/p>
走进Scheme语言的世界,你就发现法和数据结构的妙用随处可见Q可以充分的验你对算法和数据l构的理解。Scheme语言虽然是古老的函数型语a的l,但是它的里面有很多是在其它语a中学不到的东西,我想q也是ؓ什么用它作机语言教学的首选的原因吧?/p>
- 王垠的个Z?/a> Q此文章的很多东西都是从他哪里受到启发而写出来的?/li>
- 自由软g 杂志~辑z峰在他的文章中有很多对Scheme语言的评P在他开创的工程中也有很多应用?/li>
- 在此|站的Linux专区中,赵蔚?Scheme的介l?<http://www-128.ibm.com/developerworks/cn/linux/l-scheme/part2/index.html> q是比较_ֽ的?/li>
- R5RSQScheme语言的标准定义: http://www.swiss.ai.mit.edu/~jaffer/r5rs_toc.html
- GuileQGNU的扩展语aQ?http://www.gnu.org/software/guile/?Guile Reference Manual
- Scheme语言在MIT?.0001评中广泛应用,x名的 SICP Q这是该校计机U学专业的必修课之一?/li>
- Scheme语言在学校中的应用, http://www.schemers.com/schools.html Q这是一个不完全列表Q从q里可以看出SCHEME语言在教学中的重要性?/li>
宋国伟( mailto:gwsong52@sohu.com Q,目前?吉林省d惠市信息中心 从事|络l护工作Q著?《GTK+2.0~程范例?/a> 一书,热衷于Linuxpȝ上的~程及相关的研究?/p>
]]>