Scheme是一门通用编程语言,它支持多种数据结构,如字符串、list和向量等,还支持数字、字符等基本类型。 Scheme编程语言通常被认为是用来处理符号运算的绝佳选择,实际上其强大灵活的特性能胜很多领域的软件研发工作。 据不完全统计,文本编辑器、编译器优化、操作系统、图形图像、专家系统、数值计算、 金融分析和虚拟现实等软件开发领域都曾使用过Scheme编程语言,不少至今仍在使用中。
Scheme语言的学习特点比较类似围棋——易学难精。 易学是因为整个语言的语法结构和概念非常简单,而且大部分Scheme实现的开发环境都是交互式的, 非常便于实验分析;而难于精通则是因为想真正发挥出这门语言的威力需要耐心细致的分析和大量的练习。
使用Scheme编写程序的另外一个好处是程序的可移植性相当高,因为Scheme将操作系统和硬件平台的所有细节都隐藏起来了,
此外大部分Scheme实现都遵循Scheme标准委员会颁布的语言规范,目前最新的规范是R7RS(Revised 7 Report on Algorithmic Language Scheme)
。
早期的一些Scheme实现的执行效率非常低,这是由当时的硬件和软件技术的限制决定的。 当今大部分的Scheme实现采用了非常先进的编译技术,其执行效率几可媲美低级语言。 现代Scheme实现的执行效率仍然低于低级语言的主要原因是Scheme实现帮助程序员做了很多运行时检查和纠错工作, 幸运的是大部分现代Scheme实现都支持将部分检查功能关闭,从而达到进一步提高程序性能的目的。
Scheme支持多种数据对象,主要有字符、字符串、符号、list、向量、复数、实数和任意精度(受限于内存大小)的有理数。
Scheme的数据对象是根据需要动态分配的,当数据对象不再被使用时会被自动回收和释放,这一切都通过垃圾收集器(GC)
自动完成。
Scheme中的一些简单或常用的值常驻于内存中,因此不涉及分配和释放问题,这些值主要有比较小的整数、字符、布尔值及空的list。
数据对象是Scheme语言的第一类成员
,一旦分配成功以后就一直有效,除非程序不再使用它,无论其内部保存了什么类型的值。
由于数据对象在被使用期间一直有效,它可以在程序中多处共享使用,如作为函数实参、函数返回和复合类型的成员等等。
Scheme的continuation对象可用于保存一个函数的执行状态,该执行状态可以在稍后被重新唤醒并继续执行。和其它数据对象一样, continuation对象可被任意传递和返回。基于continuation可以实现线程池、协程等较为复杂的控制机制。
Scheme的宏允许开发者创建新的语法结构,宏是用来进行概念抽象和开发DSL
的强大工具,
几乎所有的大型Scheme程序都会用到宏。
Scheme是从Lisp语言发展过来的,因此它一般被认为是一种Lisp方言。Scheme从Lisp继承了大量的优秀设计思想,主要有:
- 数据对象是第一类成员;
- 支持符号和list;
- 程序可被作为对象进行使用;
Scheme还从Algol 60语言中汲取了词法作用域和块结构两大设计思想。Scheme在所有的Lisp方言中第一个实现了词法作用域、块结构、
函数作为第一成员、尾递归优化、continuation和基于词法作用域的宏(也被叫做卫生宏
)。
Common Lisp是Lisp的另外一大方言,相比Scheme它提供了更多的程序流程控制语法和库函数。然而Common Lisp不支持continuation, 并且其语言规范并没有要求实现尾递归优化,函数和变量的命名空间也是相互独立的。总体而言,Common Lisp为开发者提供了更多的语法糖, 而Scheme则贯彻了核心语言最小化的思想,只提供常用的语法结构,开发者可以使用宏对语言进行扩展。
Scheme语法
Scheme程序由关键字、变量、括号表达式、常量(数字、字符、字符串、向量字面量、list字面量、符号字面量等)、空白符和注释构成。
关键字、变量和符号被统称为标识符,标识符由字母和数字组成,可包含的特殊字符有“?!.+-\*/<=>:$%^&_~@
”以及一些unicode字符。
标识符通常不能使用@
和任何可能是数字常量前缀的符号作为起始,例如:数字、加号(+)、减号(-)和小数点(.)。
多个标识符被可被下列内容分隔开:空白符、注释、圆括号、方括号、双引号和#
。标识符内部可使用任意unicode字符,
对于分隔符我们可以使用特殊的unicode表示法“\xsv;
”,其中sv是表示字符的16进制数字。
Scheme没有对标识符的长度做限制,开发者可以使用任意长度的标识符。然而记住一点,过长的标识符不仅不能取代注释, 而且会降低代码可读性(想想冗长的java代码)。当某个标识符的有效作用域较大时,我们可以将其定义得长一些以方便辨识, 反之则应尽量使用较短的标识符。
标识符可由大小写字母混合组成,且Scheme是区分大小写的,abcde
、ABcde
和AbcDe
是三个不同的标识符。
下面给出一些合法标识符的示例:
hi
Hello
H\x65;llo
n
x
x3
x+2
?$&*!!!
+
-
...
->earth
Scheme的基本语法结构和list字面量一样都是括号表达式,如:(a b c)
和(* (- x 2) y)
,空list的写法是()
。
括号表达式既可以使用方括号也可以使用圆括号(必须成对出现),一般我们会在一些控制语句的子项中使用方括号,
目的是为了提高代码可读性。向量的写法与list类似,不过需要在前面加一个#
,例如:#(this is a vector of symbols)
。
字节向量用于保存一个字节序列(每个值的范围是0-255),它使用#vu8(
和)
将序列括起来,例如:#vu8(3 250 45 73)
。
Scheme的字符串使用双引号栝起,例如:"I am a string"
。字符则以#\
作为起始,例如:#\a
。数字有整数(-32)、
分数(1/3)、浮点数(1.3或1e23)和复数(1.3-2.7i或-1.2@73)几种写法。Scheme使用#t
和#f
分别表示布尔值的真和假。
实际上Scheme的条件判断语句会将除了#f
以外的所有值都看作为真,包括:0、”false”、()。
Scheme使用;
表示一行注释的开始,到本行末尾结束。在Scheme的编程习惯中,一段代码的注释会保持和代码相同的缩进,
以增加代码可读性。用于说明一个或多个函数的注释则一般使用多个;
作为起始,例如:;;; The following procedures ...
。
此外还有两种相对不太常用的注释语法:块注释和内嵌注释。块注释使用#|
和|#
将注释包围起来,注释文本可以跨越多行且支持嵌套。
内嵌注释则使用#;
将括号表达式中的某个字句注释掉,例如:(three #;(not four) element list)
。
Scheme的某些类型的值是没有标准的打印输出格式的,例如:函数
和port
。
后面我们使用#<description>
格式表示这样的值的打印输出,例如:#<procedure>
和#<port>
。
Scheme命名惯例
Scheme为开发者提供了非常灵活的标识符命名方式,然而对其滥用会导致整个代码的可读性严重下降。 经过大量的的开发实践,Scheme开发人员逐渐总结出了一套便于理解和实践的命名惯例,具体如下:
- 谓词以
?
结尾,例如:eq?、zero?和string=?,几个例外是数值比较操作符:=、<、>、<=、>=; - 类型判断谓词采用“
类型名称?
”这样的写法,例如:pair?、list?、number?; - 大部分操作字符、字符串、向量的函数名称的前缀都是char-、string-、vector-,例如:string-append;
- 将数据对象从某一类型转换至另一类型的函数采用“
type1->type2
”这样的写法,例如:vector->list; - 会更新或修改操作对象的关键字或函数的末尾一般都会有一个
!
,例如:set!和vector->set!,例外情况是输入输出函数;
以上惯例已经被大部分Scheme软件工程所采纳。