Lisp入门

本教程以图文
搭配的形式向您简要介绍Racket编程。这些例子很有趣,也很有启发作用,即使你不打算深入下去也值得一看。毕竟,一图胜千言,至少是五百句“hello world”。
我们假设你用DrRacket来运行这些例子,这是了解这门语言的概貌和环境的最好途径,虽然你也可以时不时地用用macs,vi,或是别的编辑器。

1、准备

下载Racket,安装,启动DrRacket。

2、设置

我们先要加载一些能生成图片的函数库才能开始画图。把下面这句复制到DrRacket顶部的窗口,即定义区:

 #lang slideshow

点击Run按钮,你会发现插入符跳入了下面的窗口,即交互区。
如果你以前用过DrRacket,你还要先将DrRacket重置为“使用源码中声明的语言”,可以在Laguage菜单下的Choose Language中找到。

3、开始

在交互窗口敲入一个表达式,回车,DrRacket就会执行这个表达式,然后打印出结果。表达式可以是值,比如数字5或者字符串“art gallery”:

也可以是一个函数调用。调用的格式是:左圆括号,函数名,参数,右圆括号。像这样:

circle函数就像打印数字和字符串那样打印出一张图片,一个圆。传入circle的参数就是圆的尺寸(像素计)。你可能已经猜到,或许有一个能打印长方形的函数,输入的参数分别是长和宽:

试着给circle传入更多的参数,看看会出现什么情况:

如你所见,若触发了错误,DrRacket会用粉红色高亮显示出错的语句。
有个叫hc-append的函数能把circle和rectangle这些函数生成的图片组合起来。你可以像这样来调用它:
这里写图片描述

hc-append中的连字符是函数名的一部分,而不是hc减去append。函数名打头的h意思是水平组合(horizontally),而接下来的c表示竖直居中(centeredvertically)。
你也许会想应该有其他类似的函数吧,比如把图片竖直摞起来左对齐的函数?把光标移到hc-append上按F1,就会弹出一个搜索页面,里面有指向hc-append文档的链接。点击链接,能发现很多相关的函数。

4、自定义

当你要反复使用某个圆或矩形时,给它们取个名字会更方便。回到定义区(顶部的窗口)添加两条定义,现在,整个定义区的内容是这样的:
这里写图片描述
再次点击Run。现在敲入c或r:
Lisp入门_第1张图片
如你所见,在传入图片参数前,hc-append函数还可接受一个数字,这个数就是图片之间的空格数。
我们也可使用define语句在交互区定义c和r。实际上,定义区里的代码一般是你的程序,在交互区作临时查看和调试。
下面我们要定义一个函数。和定义形状一样,我们用define来定义函数,但要用圆括号把函数名和参数括起来:

调用函数和定义函数的语法是一样的

不管是交互区还是定义区,上面的语句都能被执行。程序一旦跑起来,定义区的执行结果就会出现在交互区。从现在起,我们把示例中的定义和表达式写在一块,你把它们怎么放都行。但我们后面的例子会用到前面的定义,所以你最好把定义都放在定义区。

5、局部绑定

define还可用于局部绑定,比如,在函数内部定义函数。
Lisp入门_第2张图片

但Racket程序员一般用let或let*来实现局部绑定。用let的好处就在于可以把它放在表达式的任何位置上,而且可以一次绑定多个定义,而不是每个定义都要define一下。

let可以一次绑定多个定义,但这些定义间不能相互引用。let*则相反,它允许后面的绑定使用前面的:

6、函数即值

我们试试把circle当作一个表达式而非一个函数去执行,看会发生些什么:

就像c与一个圆绑定,标识符circle与一个函数绑定。同圆形不同的是,没有简便的办法把这个函数完整的显示出来,所以DrRacket打印了这么一句#。
这个例子说明,函数同数字、图片一样,都可以看作某种值(即便它们不能很好的打印出来)。既然函数也是值,你就可以把它们作为参数传给其他函数:

Lisp入门_第3张图片
如果调用的函数需要函数作参数,而作参数的那个函数在别处又用不上,这时用define来定义函数就比较烦人了,你得取个名字而且找个地方下定义。另一种方法就是用lambda生成一个匿名函数:
Lisp入门_第4张图片

lambda之后用括号括起来的是函数的参数,在函数名之后的表达式是函数的定义。之所以用lambda而不是function或是procedure,部分是历史遗留的原因,部分是由于Racket的文化。
用lambda来定义那些作为值的函数会很简洁。比如,series可以这样定义:
这里写图片描述

大多数Racket程序员更喜欢用define而不是lambda来定义函数。

7、文本作用域

Racket采用文本作用域(译注:作用域的作用就是区分程序中不同部分的变量One of the basic reasons for scoping is to keep variablesin different parts of the program distinct from one another.。lexical scope(见http://en.wikipedia.org/wiki/Scope_(programming)),也叫静态作用域(static scope),是一种根据语言文本的位置确定变量引用的规则),就是说某个标识符绑定什么值是由程序文本决定的,这条规则也适用于lambda中的标识符。
举个例,lambda中的mk就是rgb-series函数的参数mk:

这还有一个例子,rgb-maker接受一个函数然后返回用传入的函数生成一个新函数。

注意rgb-maker创建函数和rgb-series的不同。

8、模块

在你的定义窗口,程序是这么开头的

 #lang slideshow

定义窗口内的所有代码都是属于一个模块。这个模块会在初始时导入名叫slideshow的模块中的所有东西,比如图片生成函数和一些常用函数,像list,map。
要导入额外的库,需要用到require语句。例如,slideshow/flash库提供了一个叫filled-flash的函数:
Lisp入门_第5张图片

模块有好几种命名、发布方式:
有些模块是Racket发行版自带的,安装在collects目录下。例如,模块slideshow/flash的名字就暗示了其源文件是slideshow目录下的flash.rkt。如果模块名中没有斜杠,它们就是指main.rkt文件。
一些模块是通过PLaneT服务器发布的,它们可以通过命令行自动下载。例如,你第一次执行下面这段代码时:
Lisp入门_第6张图片
DrRacket会自动下载random.plt库的1.0版并导入random.rkt模块。
一些模块依赖于其他模块,但不是必须属于某个特定的容器或代码包。例如,你可以把前面的所有定义都放进一个叫quick.rkt的文件里,然后加入这么一行

      (provide rainbow square)

然后,你再在quick.rkt所在目录新建use.rkt文件,键入:

运行use.rkt,一个彩虹条块就会显示出来。注意,use.rkt仅仅导入了racket,racket本身并不包含任何生成图片的函数,而是提供了require语句和函数调用语法。
Racket用户喜欢把程序和库写成模块,通过相对路径来相互引用,或从容器和planet中导入已经写好的库。这种程序开发方式还方便了他人,可以把写好的程序打成包上传到PLaneT服务器上,或是以安装文件的形式发布(译注:还有半句不知怎么翻好,in either case without modifying the internalrelative references among modules)。

9、对象

对象系统是另一个值得Racket用户学习的关于语言扩展的复杂例子。在图形用户界面领域,对象比函数(甚至是lambda)更好用。Racket的图形用户界面接口和图像系统就是用对象和类实现的。
类框架自身是由racket/class库实现的,racket/gui/base库提供了图形用户界面和绘图的有关类。这些类的命名习惯以%结尾:
Lisp入门_第7张图片

new语句创建一个类实例,并对label、width等参数进行初始化。send语句调用对象的一个方法,show,参数#t表示逻辑常量“真”。
就像把一块画布嵌进画框里,图形化工具箱中的画图命令把slideshow生成的图片渲染成一个可置于上下文中的图样。我们可以用slideshow中的make-pict-drawer函数以回调的方式把一幅图片融入画布中:
Lisp入门_第8张图片

每个画布都会把画框填满,这是画框管理置于其中的物件的默认方式。

你可能感兴趣的:(lisp)