Exercise 4.14
1、Eva的map能够工作是应该的,因为Eva在求值器中定义了map。
2、那么Louis的map为什么不能工作呢?
首先,让我们来看一下map的基本使用方式(在scheme中,而不是在求值器中):
(map (lambda (x) (* 2 x)) '(1 2 3 4))
=> (2 4 6 8)
可以看出map函数的使用方式和其他作为基本过程安装的函数(如cons,cdr等)的区别是它接受一个函数作为参数。好的,假设我们按照louis的方式实现map,现在我们来看看当我们的求值器接受上面的求值器时,会发生什么:
1)根据求值器规则,我们首先求值操作符map和参数的值:
;;; M-Eval input:
map
;;; M-Eval value:
(primitive #<procedure:mmap>)
;;; M-Eval input:
(lambda (x) (* 2 x))
;;; M-Eval value:
(compound-procedure (x) ((* 2 x)) <procedure-env>)
;;; M-Eval input:
'(1 2 3 4)
;;; M-Eval value:
(1 2 3 4)
作为对比,我们来看一下它们在scheme中的值:
> map
#<procedure:mmap>
> (lambda (x) (* 2 x))
#<procedure>
> '(1 2 3 4)
(1 2 3 4)
2)在求值过程调用时,求值器看到(primitive #<procedure:mmap>),它会取出系统的map版本,即#<procedure:mmap>,然后把它应用在参数(compound-procedure ...)上,这时map就会报错:
procedure application: expected procedure, given: (procedure (x) ((* 2 x)) ……
看到了吧,系统函数map期待的是#<procedure>类型的过程,而我们给的是(procedure (x) ((* 2 x)) 这样的列表。这个列表是由我们的求值器中的函数产生的:
(define (make-procedure parameters body env)
(list 'procedure parameters body env))
因为系统版本的map不知道如何处理我们的求值器产生的过程,因此系统版本的map不能正常工作。
3,如何正确实现map?
或许最好的理解方式是像Eva一样自己实现一个map:
;; Exercise 4.14
(define (eval-map exp env)
(let ((proc (cadr exp))
(alist (cddr exp)))
(apply-in-underlying-scheme
map
(lambda (x) (apply (eval proc env) (list x)))
(list-of-values alist env))))
(put 'map eval-map)
测试结果:
;;; M-Eval input:
(map (lambda (x) (* x 2)) '(1 2 3))
;;; M-Eval value:
(2 4 6)
注意:这里实现的map只是最基本的map函数,它只能接受一个列表,因为我们的求值器处理任意多个的参数的情况,像下面这样的方式map不能工作:
(map + '(1 2 3) '(4 5 6))