lisp 方言之 Scheme 简单学习

作者: | 更新日期:

之前只知道 lisp 是门很古老的语言,想着没有机会接触了。现在看 SICP 这本书时,发现还是有必要学一下这类语言的。

lisp 背景

Lisp 语言的历史已经很久了,几乎与 Fortran 一样长。
二十世纪五十年代,计算机科学家先是发明了针对数字计算的 Fortran 语言,后来针对符号计算,由MIT 的John McCarthy于1960年开发出了Lisp(List processing)语言。
该语言原来是为表处理而设计的编程语言,后来广泛用于处理人工智能问题。
Lisp 程序中充满了一对对嵌套的小括号,这些嵌套的符号表达式体现着递归。
递归是数学上的基本概念之一,从递归理论出发,一切可以计算的函数最终都可以划归为几种基本的递归函数的种种组合。

Lisp 重要的特征就是:
第一,基于 Lambda 演算的计算模型;
第二,加上 List processing,这也是 Lisp 名称的由来;
第三,直接在抽象语法里面工作, 这是非常特别的。
前两个重要特征,是 McCarthy 天才的设计,第三个特征则是有趣的巧合。

1994年时众多 Lisp 版本又得到了相当的统一,统一之后的版本称为Common LISP。
Common Lisp 含有非常丰富的库,仅仅语言的规范就长达千页以上,包括面向对象的 CLOS。

Scheme 语言是 Lisp 的一个现代变种、方言,诞生于1975年,由 MIT 的 Gerald J. Sussman and Guy L. Steele Jr. 完成。
Scheme语言的规范很短,总共只有50页,甚至连Common Lisp 规范的索引的长度都不到,但是却被称为是现代编程语言王国的皇后。
它与以前和以后的 Lisp 实现版本都存在一些差异,但是却易学易用。

Scheme遵循极简主义哲学,以一个小型语言核心作为标准,加上各种强力语言工具(语法糖)来扩展语言本身。

语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。
通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

语法盐(英语:syntactic salt)是指在计算机语言中为了降低程式设计师撰写出不良程式码的设计,但其中仍会有潜藏错误存在的可能。
例如,C语言或C++语言中Switch指令的case中若不加break编译器并不会产生错误讯息,部分程式设计师认为宣告变量型态也是语法盐的一种。

Scheme 特点

Scheme语言具有它独特的魅力,看看Scheme的语法特点:

Scheme 数据结构

Scheme 语法

注释

分号开始一段注释。

;this is a comment  

常量

常量表达式返回本身的值。

3.14	; 返回 3.14  
#t	; 返回布尔值 #t  
#\c	; 返回字符 #\c  
"Hi!"	; 返回字符串 "Hi!"  

表达式记法

Scheme的表达式的写法有些特别,表达式用括号括起来。
括号里面的第一个出线的是函数名或者操作符,其它是参数。
Scheme的这种表达式写法可以叫做前置式。

下面是一些Scheme的表达式的例子以及其对应的C语言的写法。

 Scheme                               C
------------------------------------------------------------------  
(+ 2 3 4)                       (2 + 3 + 4) 
(< low x high)                  ((low < x) && (x < high)) 
(+ (* 2 3)                      (* 4 5)) ((2 * 3) + (4 * 5)) 
(f x y)                         f(x, y) 
(define (sq x) (* x x))         int sq(int x) { return (x * x) } 

赋值

** let 表达式 **
let 表达式的赋值只在表达式内部有效。

(let ((x 2) (y 3)) (+ x y))  

; ==>  

(let 
    (
        (x 2) 
        (y 3)  
    )
    (+ x y)  
)
; 先赋值: x=2, y=3,再计算x+y的值,结果为5。  
; 注意 (x 2) 和 (y 3) 外还有一层括号, 即和 let 同一级  

** define 和 set! 赋值 **

define和 set! 表达式的赋值在全局有效。

define 和 set! 的区别是define既能赋值又能定义变量,而set!只能对已经定义的变量赋值。

(define a 1)	 
a				
; return 1

(set! a 2)  
a				
; return 2

(set! b 1)			
; 错误,b尚未定义  

函数

** lambda 表达式 **

(lambda (var ...) exp1 exp2 ...)  

lambda 表达式用于定义函数。var … 是参数,exp1 exp2 …是函数的执行 部分。

((lambda (x) (+ x x)) (* 3 4))  
; return 24  

我们可以把 lambda 表达式理解为匿名函数即可。

** define 定义函数 **

(define (double x) (+ x x))  

; double is function name  
; x is 参数  

实际上上面个的 define 定义函数等价于

(define double (lambda (x) (+ x x)) 

然后我们可以看到, 我们可以把函数赋值给一个变量。

所以也可以这样做。

(let ( 
        (double (lambda (x) (+ x x))) 
    ) 
    (double 12) 
)

顺序计算

(begin exp1 exp2 ...) 顺序执行表达式 exp1, exp2, …,返回最后一个表达式的结果

条件表达式

** 关系运算符 **

(< -1 0)  		#t  
(> -1 0)  		#f 
(eqv? 'a 'a) 	#t  

** 逻辑运算 **

(not #t)  		#f  
(not #f) 		#t 

(not 1)  		#f  
(not '(a b c))  #f 


(or)  			#f  
(or #f)  		#f  
(or #f #t)  	#t  
(or #f 'a #f)  	a 

(and)  			#t  
(and #f)  		#f  
(and #f #t)  	#f  
(and #f 'a #f)  #f  
(and 'a #t 'b)	'b  

** if 表达式 **

(if test consequent alternative) 如果test表达式为真,返回 consequent,否则返回 alternative。

(define (abs n)  
    (if (< n 0)  
        (- 0 n)  
        n)) 

** else 表达式 **

(cond (test exp) ... (else exp)) 多路分支判断表达式,类似于C语言的 “if … else if … else”。

(define abs  
  (lambda (n)  
    (cond  
      ((= n 0) 0)  
      ((< n 0) (- 0 n))  
      (else n)))) 

** case 表达式 **

(let ((x 4) (y 5))  
  (case (+ x y)  
    ((1 3 5 7 9) 'odd)  
    ((0 2 4 6 8) 'even)  
    (else 'out-of-range)))  
; 返回 odd  

循环

** do 表达式 **

(do ((var1 val1 update1) ...) (test res ...) exp ...) 类似于C语言的for循环。先将val1赋值给var1,…,之后循环开始,在每次循环的开始,先执行表达式 test,如果返回布尔值真,则循环终止,并返回结果 res,如果表达式 test返回布尔值#f,则运行表达式 exp…,之后依次用 update1 … 的值来为变量 var1 … 重新赋值。

;计算阶乘 n! = n*(n-1)!  

(define factorial  
  (lambda (n)  
    (do ((i n (- i 1)) (a 1 (* a i)))  
        ((zero? i) a)))) 

(factorial 10)  
; 返回 3628800 

** map 表达式 **

(map procedure list1 list2 ...) 列表 list1 list2 … 必须具有同样的长度;过程 procedure 接受的参数个数同列表的个数,各个列表中对应的变量分别作为过程 procedure 的参数被执行, 将每次的运算结果以列表形式返回。

(map abs '(1 -2 3 -4 5 -6)) 	
; 返回 (1 2 3 4 5 6),abs接受一个参数  

(map (lambda (x y) (* x y))  
     '(1 2 3 4)  
     '(8 7 6 5))		
;返回(8 14 18 20) ,lambda (x y) 接收两个参数  

** for-each 表达式 **

(for-each procedure list1 list2 ...) 同 map表达式, 但是不返回结果列表。

点击查看评论

关注公众号,接收最新消息

关注小密圈,学习各种算法

tiankonguse +
穿越