Schemer
简单而直接的方式表达编程思维,探究计算的本质。
搭建 Scheme 环境
推荐使用R. Kent Dybvig大神的 Chez Scheme 作为环境,还有他的书 The Scheme Programming Language 作为学习资料。
我使用的是 Ubuntu 18.04,下载好 Chez Scheme 的代码后,需要安装一些依赖,我目前碰见的如下:
sudo apt-get install uuid-dev
sudo apt-get install libncurses5-dev libx11-dev编译安装:
$ ./configure
$ make
$ sudo make install启动交互界面如下所示,就表示一切准备就绪了:

概念预习
语言思想
LISP 语言诞生时,就包含 9 种新思想,现代语言吸收了 7 种,还有 2 种依然是 LISP 独有。
条件结构
if - then - else,当时是独有,目前基本上是编程语言常识函数也是基本数据类型
递归
变量都是指针(内存地址),没有数据类型,变量指向的值有数据类型。
复制变量相当于复制指针,而不是复制它们指向的数据。学过 C 语言的指针和 64 位汇编语言,查看代码应该很好理解
程序只由表达式
expresion组成。这和现代语言不同,现代语言由表达式和语句statement组成。“表达式”是一个单纯的运算过程,总是有返回值
“语句” 是执行某种操作,没有返回值
垃圾回收机制
符号类型
symbol,符号实际上是一种指针,指向储存在哈希表中的字符串。所以,比较两个符号是否相等,只要看它们的指针是否一样就行了,不用逐个字符地比较。(目前我还不理解)代码使用符号和常量组成的树形表示法
Lisp 并不真正区分读取期、编译期和运行期。你可以在读取期编译或运行代码;也可以在编译期读取或运行代码;还可以在运行期读取或者编译代码。
在读取期运行代码,使得用户可以重新调整 LISP 的代码
在编译期运行代码,则是 LISP 宏的工作基础
在运行期编译代码,使得 LISP 可以在 Emacs 这样的程序中,充当扩展语言
在运行期读取代码,使得程序之间可以用
S-expression通信
函数是“一等公民”
“一等公民”:first class,函数是基本数据类型之一,可以赋值给其他变量,可以作为入参,可以作为返回值。
举个JS中的例子,print函数的定义与使用:
var print = function(i) {
console.log(i);
}[(1, 2, 3)].foreach(print);没有“副作用”
副作用:side effect,含义是函数执行后,除了返回值以外,还改变了函数外部的状态(比如修改了全局变量)。
引用透明
“引用透明”:Referential transparency,函数的运行不依赖于外部变量或“状态”,只依赖于输入的参数,任何时候只要参数相同,函数的返回值总是相同的。
接近自然语言,易于理解
( 1 + 2 ) * 3 / 4写成函数式代码:
substract( multiply( add( 1, 2 ), 3 ), 4 );变形后:
add( 1, 2 ).multiply( 3 ).substract( 4 );基础表达式
基本类型
Schemer 支持数字(整数、IEEE 浮点数、复数),字符串,列表。
123456789987654321 => 123456789987654321 # 整数
3/4 => 3/4 # 分数
2.718281828 => 2.718281828 # 小数
2.2+1.1i => 2.2+1.1i # 复数表达式
无论是函数或是操作符+ - * /,都使用前缀表达式,只用括号表达计算顺序,所以没有 C 语言中那么多的算术计算优先级需要记忆。
( produce arg1 arg2 ... ) # 格式
列表
列表是多个基本数据构成的序列,不要求每个元素的类型相同,并支持嵌套。
列表的表示格式为: '(arg1 arg2 ...),注意 () 前面的单引号,这是为了与表达式(produce arg1 ... )区分而设计的,使用 ' 提示编译器把(arg1 arg2 ...)当作一个列表整体,而不是求值表达式。
列表 car 与 cdr 操作
car 是取列表第一个值,cdr 是去除第一个值后,返回剩余列表,注意car是返回单个数据,cdr是返回列表。
列表 cons 与 list 操作
cons是将一个数据插入到列表的头部,而list则是将多个数据构成一个列表后返回。
事实上,熟悉数据结构话很容易推知,列表的底层实现是链表。
表达式求值
有了上面基础,可知(produce arg1 ...)有个返回值,这个值可以继续作为外部(proc arg1 ...)的参数进行下一步求值,但如果它是在外部produce位置返回的呢?那它就作为下一步计算的produce。也就是我们说的函数作为first class,是可以作为返回值的含义。
下面代码中(car (list + - * /))的求值结果是+,它作为(produce 2 3)中的produce进行下一步求值,最终结果是5。
变量
Schemer 中的变量需要通过let表达式绑定,然后在后续的表达式(此处称为body)中可以使用,(注意,这句话的意思是let变量是局部的),格式如下:
(let ( (name value) (name value-expr) ...) body1 body2 ... )
Schemer 中[] 等价于 (),只要求配对使用,所以可以通过[]改善代码可读性。
( let ( [x 2] ) (+ x 3) )
( let ( [x 2] [y 3] ) (+ x y) )let表达式是可以嵌套的,body还可以是另一个let表达式,外层定义的变量在内层let表达式中可见;但是,当内层let表达式定义了同名的变量时,外层变量会被遮蔽,此时可见的只有内层同名变量。(同 C 语言机制一样)
lambda 表达式
lambda 表达式用于创建一个函数,它的返回值就是一个函数(可以放在表达式produce位置),接受一个参数列表,它有与let表达式一样的body,格式如下:
(lambda (var ...) body1 body2 )可以直接把lambda表达式当作匿名函数使用,也可以绑定到一个变量名(相当于命名函数),然后在body中多次调用。
再看看lambda表达式的闭包性质,lambda的body内部可以直接使用外部变量,但要注意的是,一旦lambda表达式执行完毕,返回函数时,此时外部变量的值已经固定在函数内部了,这时再修改外部变量,是不会影响到lambda函数内部值的。
全局变量
使用define表达式可以定义在整个程序可见的变量与函数,但要注意,全局变量也会被同名局部变量遮蔽。(同 C 语言机制一样)

条件判断
(if (operator arg1 arg2) true-value false-value) # 格式