Program Structure 笔记11 (简单求值器的实现)
(作者:colinboy Email:cybbh@163.com) 2008.5.21
(内容难免出现错误或一些专业词汇使用不当,只是个人笔记,能理解总体内容就好)
我们要实现一个求值器,能计算类似下面的表达式:
(+ 1 2 3 4 5)
(+ 1 (+ 2 3) 4)
(+ (+ 1 2) (* 3 4))
此求值器只实现了+ - * /四种基本运算.
简单求值器完整代码:
;Read and print loop
(define (calc)
(display "Calc: ")
(flush)
(print (calc-eval (read)))
(calc))
;Evaluate an Expression
(define (calc-eval exp)
(cond ((number? exp) exp)
((list? exp) (calc-apply (car exp) (map calc-eval (cdr exp))))
(else (error "oh my god!" exp))))
;Apply function to some arguments.
(define (calc-apply fn args)
(cond ((equal? fn '+) (accumulate + 0 args))
((equal? fn '*) (accumulate * 1 args))
((equal? fn '-) (cond ((null? args) (error "bad args!"))
((= (length args) 1) (- (car args)))
(else (- (car args) (accumulate + 0 (cdr args))))))
((equal? fn '/) (cond ((null? args) (error "bad args!"))
((= (length args) 1) (/ (car args)))
(else (/ (car args) (accumulate * 1 (cdr args))))))
(else (error "bad operator"))))
测试:
STk> (calc)
Calc: (+ 1 2)
3
Calc: (* 2 (+ 1 2))
6
Calc: (/ 2)
0.5
此求值器的结构为:
1. calc,循环执行,calc接受输入表达式然后传递给eval.
2. eval,把输入的表达式转换成apply调用.
3. apply,根据传入的操作符和参数计算出结果.
在第一部分中,有个(read)调用,read是读入用户输入的数据,例如用户输入:
(+ 1 (+ 2 3) 4)
read会把输入的数据组织成一个list,对于上面的输入,产生的list为(+ 1 + 2 3 4).
我们可以编写一个测试read的函数
(define (readinf inf)
(if (null? inf) '()
(se (car inf) (readinf (cdr inf)))))
测试如下:
执行 (readinf (read))
输入:(+ 1 2 (* 3 4) (/ 5 6) 7)
输出:(+ 1 2 * 3 4 / 5 6 7)
执行(readinf (read))
输入: (+ '1 '2 (/ 3 4) '5)
输出: (+ quote 1 quote 2 / 3 4 quote 5)
对于'1这样的输入,会首先读入',然后读入1.
其实read并不会计算任何表达式的值,它仅仅只是按照输入的字符读入然后存储到表中,对如+这种操作符只是当作'+字符处理.
read不会存储括号,而是忽略掉,对于如(+ 1 2 (* 3 4) (/ 5 6) 7)这种输入,read读入后组成sentence为(+ 1 2 * 3 4 / 5 6 7).
eval的作用为把输入的表达式中的参数计算成值.
((list? exp) (calc-apply (car exp) (map calc-eval (cdr exp))))这句表示调用apply,并且遵循应用序的规则.
所以我们实现的求值器是一个应用序的求值器!
对于如(+ (+ 1 2) (+ 3 4) 5), 调用apply,第一个参数为'+,第二个参数为一个表,内容为所有参数的值.所以在执行此次调用之前会首先计算出(+ 1 2)和(+ 3 4),然后再把计算出的值作为参数计算出最后的结果.
我们实现的求值器并不和scheme的求值器一样,因为在scheme的求值器中,调用apply的第一个参数为一个函数,而不是像我们实现的这种为一个字符.
但是此求值器的结构是和scheme的求值器十分类似的.
仔细观察我们发现,calc没有遵循函数式程序设计规则,因为每次调用calc,可能会产生不同的结果.(例如第一次调用计算的是(+ 1 2),而第二次调用计算的是(+ 3 4),就会产生不同的结果).
但是calc-eval和calc-apply是遵循函数式程序设计规则的.
一些自求值的表达式:
'1
1
#t
#f
"string"