关于SICP中练习2.83与练习2.84

这两道练习加在一起,就是书中第135页上提到的,一种关于apply-generic过程改写思路的具体实现。其实,练习2.83相对来说更简单,我就先直接放代码了:

; 安装类型提升包
(define (install-raise-package)
    ; 安装从整数转换到有理数的过程
    (put 'raise '(scheme-number)
        (lambda (n) (make-rational (contents n) 1)))
    
    ; 安装从有理数转换到实数的过程
    (put 'raise '(rational)
        (lambda (r) (attach-tag 'real (div (numer r) (denom r)))))
    
    ; 安装从实数转换到复数的过程
    (put 'raise '(real)
        (lambda (x) (make-complex-from-real-imag (contents x) 0)))
    'done)

; 类型提升器
;   scheme-number -> rational
;   rational -> real
;   real -> complex
(define (raise x) (apply-generic 'raise x))

这里需要注意的就是有理数做类型提升的时候,div中的numer与denom过程是之前我在做练习2.79与练习2.80那篇文章中所定义的有理数选择器,原因我也在那篇文章中说过了,这里就不再重复了。还有一点,就是这里作者要求我们再多考虑一种实数类型进去。我在这里也只是做了简单的处理,就是给一个只要是没有虚部的数打上real标签就算作它是实数了,而没有从头开始去写一个实数模块,因为感觉实在没有这个必要。
到这里,练习2.83算是完成了,接下来是重头戏练习2.84。其实,这道练习完全就是看设计的,一旦把类型塔这个东西设计好了,那么题目自然就变得非常简单了。而且,又因为练习2.82中的表处理经验,还可以很轻松地去处理任意个参数的情况,而不是仅限于书中两个参数的特殊情况了。
其实,问题最核心的部分就是如何去描述一个类型塔,那就从建立一个类型塔开始。先放代码:

; 制作一个层级管理器
(define (make-rank-manager)
    ; 类型的层级塔
    ; 层级越高 编码越大
    (define type-tower (list))
    
    ; 创建层级对象
    (define (make-floor type hierarchy)
        (cons type hierarchy))
    
    ; 得到层级对象的层级
    (define (get-hierarchy f) (cdr f))
    
    ; 得到层级对象的类型
    (define (get-type f) (car f))
    
    ; 查找类型所在的层级
    (define (find-hierarchy type)
        (let ((this (filter
                        (lambda (f)
                            (eq? type (get-type f)))
                        type-tower)))
            (if (null? this)
                (error "Non-existent type--" type)
                (get-hierarchy (car this)))))
    
    ; 查找相应层级所对应的类型
    (define (find-type hierarchy)
        (let ((this (filter
                        (lambda (f)
                            (eq? hierarchy (get-hierarchy f)))
                        type-tower)))
            (if (null? this)
                (error "Non-existent hierarchy --" hierarchy)
                (get-type (car this)))))
    
    ; 添加层级
    (define (append-floor! type hierarchy)
        (let ((same (filter
                        (lambda (t) (eq? type t))
                        (map get-type type-tower))))
            (if (null? same)
                (set!
                    type-tower
                    (cons (make-floor type hierarchy) type-tower))
                (error "existent type--" type))))
    
    ; 选择操作
    (define (operation op)
        (cond ((eq? op 'find-hierarchy) find-hierarchy)
            ((eq? op 'find-type) find-type)
            ((eq? op 'append-floor!) append-floor!)
            (else (error "Non-existent operation--" op))))
    
    operation)

首先,这里的灵感来源于我在做书中的练习2.74的时候。那个时候,要求为一个贪得无厌的企业做一个人事文件的管理系统,那个企业旗下的每一个子公司都有一套自己的人事数据文件,然后需要做一个开放式的总管理器,进行统一管理。当时,我的想法是,先做出一个总的文件资源管理器,然后再逐个分派。其实,这个资源管理器的核心就是一个列表管理器;用它你可以实现查询,增加等基本的操作;而列表里面的内容也很简单,就是一些由子企业的名字,和与其对应的人事文件的地址所组成的键值对。而那个文件管理器正好可以用来做我们的这个类型塔,而且代码结构几乎完全相同,只要把名字换一下就可以了。
这里就需要面向对象的思维了。虽然,我用了一个高阶过程实现了它,而且书中管这个叫消息传递模型,但是,这里还是要认为它是一个类,只是这样写显得比较原始一点而已,没有像java中那样给我们封装得那么漂亮而已。
接下来,我们就用java的说法来解释这个高阶过程。首先,它是一个层级管理的类,在这个类中有一个非常重要的属性,就是列表type-tower。它存储着整个类型塔的所有信息。然后,通过make-floor、get-hierarchy 、get-type这三个过程,又相当于定义了一个内部类,这个类用于封装类型塔中的每一层。它其实就是一个键值对,由类型标签与层级值组合在一起。说到这个层级值,我确实纠结了好一会儿,最终确定只用一个数值去表示它,好处主要有一下几个方面:
首先,数值便于比较,通过解释器自带的算数系统就可以比较大小,进行运算等等。然后,便于后期的扩展操作。比如你想在类型塔中再加入一种无理数,显然,它的位置应该和有理数等价,位于相同的层级,那么可以直接方便地把有理数的层级值提取出来赋给它就完了,而完全不用去动塔中的其他键值对;还有比如你要打算在实数和有理数中间再插入一层新的数的类型,那么只要把实数的层级值与有理数的层级值取个平均值,然后再赋值给它就完了,因为层级值的数值大小就已经确保了该类型在类型塔中的位置,所以就无需改动类型塔中任何其他的成员了,这也是题目中的要求。(而且,理论上讲这种插入可以是无限的,因为两个不相等的实数之间,总是存在着无穷多个实数,有点戴德金分割的味道)
接下来就是一些简单的查找与添加的操作。最后,是一个用于分派的过程operation,它被外部过程返回,并以传入相应过程名字的方式就可以调用内部过程,从而操作其中的用于表示类型塔的列表type-tower。这个方法就好比是make-rank-manager过程提供给外部的一个公共接口,拿面向对象的话说就是:只要在过程operation中提供了选择的方法或属性,都是类内的public成员,而没有的则是private的。接下来我们写一些接口,用于对这个类进行实例化:

; 操作接口
(define rank-manager (make-rank-manager))
(define get-rank (rank-manager 'find-hierarchy))
(define get-type (rank-manager 'find-type))
(define put-rank (rank-manager 'append-floor!))

上面的第一个过程就相当于new出了一个名字叫作rank-manager的类make-rank-manager的实例化对象,然后通过这个对象,封装了一些类内的方法,使得可以被外界调用。比如过程 get-rank 如果写成java的形式就应该是:
rankManager.findHierarchy()
接下来我们给这个类型塔进行初始化:

; 创建一个数集类型塔
; 层数等级由一个基础数值控制
; 数值越大 层级越高
(define (make-number-tower)
    (put-rank 'scheme-number 1)
    (put-rank 'rational 3)
    (put-rank 'real 5)
    (put-rank 'complex 7)
    'done)

这里的数值可以随意写,只要大小能够保证相应的层级结构关系就可以了。
最后的一项工作就是写一个用于能连续提升类型到制定层级的提升过程:

; 让数据提升到给定的类型层级
(define (raise-it data hierarchy)
    (let ((rank (get-rank (type-tag data))))
        (if (= hierarchy rank)
            data
            (raise-it (raise data) hierarchy))))

这里用尾递归实现非常重要,因为当数据就是制定层级的类型的时候就不用提升,直接返回就可以了。也许你会问,这个过程有bug,就是当数据的类型已经超过了制定的层级的时候不是就返回错误了吗?虽然,raise-it作为一个独立过程来说,是应该要对参数先进行足够的检验以保证其健壮性,但是,接下来在apply-generic中将会看到,如果只是服务于它的话,raise-it过程写成这样就已经足够了,因此,我们也可以把raise-it过程写成apply-generic中的一个内部过程。接下来是apply-generic的代码:

(define (apply-generic op . args)
    
    (define (no-method-error tags)
        (error
            "No method for these types"
            (list op tags)))

    (let ((type-tags (map type-tag args)))
        (let ((proc (get op type-tags)))
            (if proc
                (apply proc (map contents args))
                (let ((type-top
                            (apply max (map get-rank type-tags))))
                    (let ((new-args
                                (map (lambda (x) (raise-it x type-top)) args)))
                        (let ((new-type-tags (map type-tag new-args)))
                            (if (equal? new-type-tags type-tags)
                                (no-method-error type-tags)
                                (apply apply-generic (cons op new-args))))))))))

通过比较之前练习2.82中的apply-generic的主要逻辑部分,可以发现这里的apply-generic的主要逻辑框架基本也没怎么改动。首先。还是一样地进行正常分派;如果正常分派行不通了,再找到参数列表中所有类型的最高层级值(层级值用基本数值表示的好处这里就体现出来了)。然后将所有的参数依据这个最大层级值开始进行连续提升(这里就可以看出来上面提到的raise-it过程的那个bug完全就不影响),得到提升后的新参数列表中的参数的类型全部都会位于类型塔中相同的某一层上了,最后,就是和练习2.82中一样的检验与分派了。
至此,全部完成练习2.83与练习2.84。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值