2.4.2 标签化的数据

2.4.2 标签化的数据
观察数据抽象的一个方式是作为最少提交的原则的一个应用。在2.4.1部分中复数系统的实现中,
我们能够使用Ben的表示方法与Alyssa的表示方式。抽象的隔离的形式是通过使用选择子和组装子,
来允许我们把我们的数据对象的具体的表示方式的选择行动推迟到最后的时机,
因此在我们的系统设计中保留了最大的灵活性。

最少提交的原则,能够被实施得更彻底一些。如果我们愿意的话,我们能够保持对表示方法的随意选择,直到
我们已经设计了选择子和组装子之后,再选举使用表示方式。如果表示方式都包括在一个单独的系统中,
然而,我们将需要一些方式来区别极坐标形式和平面坐标形式。
否则,如果我们被问到,例如,找到数对(3,4)的长度。我们不知道结果是5(用平面坐标形式解释数)
还是3(用极坐标形式解释数)。完成这种区别的自然的方式是包括一个类型的标签,符号rectangular 或者polar
作为任何一个复数的一部分。 然后当我们需要操作一个复数时我们能够使用标签来决定应用哪个选择子。

为了操作标签数据,我们将假定我们有程序type-tag 和contents 来从一个数据对象中抽取标签和实际内容。
这实际内容在复数的案例中,是极坐标或者是平面坐标。 我们也追加一个程序attach-tag来把一个标签和内容,
绑定起来,生成一个标签的数据对象。实现这个任务的一个自然的方式是使用普通的列表结构。

(define (attach-tag type-tag contents)
   (cons type-tag contents))

(define (type-tag datum)
    (if (pair? datum)
        (car datum)
        (error "Bad tagged datum -- TYPE-TAG" datum)
    )
)

(define (contents datum)
    (if (pair? datum)
        (cdr datum)
        (error "Bad tagged datum -- CONTENTS" datum)
    )
)

使用这些程序,我们能够定义判断式 rectangular? 和polar? 它们相应的识别
极坐标和平面坐标的数。

(define (rectangular? z)
     (eq? (type-tag z) 'rectangular))

(define (polar? z)
     (eq? (type-tag z) 'polar))

具有了类型标签,Ben和 Alyssa现在都能够修改他们的代码,为了让他们的不同的表示方式能
共存于一个系统中。当Ben组装一个复数时,他标签它是平面坐标的表示形式。
当Alyssa组装一个复数时,她标签它是极坐标的表示形式。 此外,Ben和 Alyssa
必须确保他们的程序的名称没有冲突。实现这个目标的一种方式是对于Ben来说是给他的表示方式的程序
都加上后缀rectangular. 对于 Alyssa来说,是给她的表示方式的程序加上后缀polar.如下的程序是
从2.4.1部分修改后的Ben的平面坐标表示试的修改版。

(define (real-part-rectangular z) (car z))
(define (imag-part-rectangular z) (cdr z))
(define (magnitude-rectangular z)
      (sqrt (+ (square (real-part-rectangular z)) (square (imag-part-rectangular z)))
      )
)

(define (angle-rectangular z)
      (atan (imag-part-rectangular z) (real-part-rectangular z))
)

(define (make-from-real-imag-rectangular x y)    (attach-tag 'rectangular (cons x y)) )

(define (make-from-mag-ang-rectangular r a)
   (attach-tag 'rectangular (cons (* r (cos a)) (* r (sin a))
                             )
   )
)


这是Alyssa的极坐标表示法的修改版。

(define (real-part-polar z) (* (magnitude-polar z) (cos (angle-polar z))))
(define (imag-part-polar z) (* (magnitude-polar z) (sin (angle-polar z))) )
(define (magnitude-polar z) (car z))
(define (angle-polar z) (cdr z))
(define (make-from-real-imag-polar x y) (attach-tag 'polar (cons (sqrt (+ (square x) (square y)))  (atan y x)
) ))
(define (make-from-mag-ang-polar r a)
   (attach-tag 'polar (cons r a) )
)

任何一个通用的选择子都被实现为这样的程序,这个程序检查参数中的标签,
然后调用相应的程序来处理那种类型的数据。例如,为了得到一个复数的实部,
real-part来检查标签,来确定是使用Ben的real-part-rectangular还是使用 Alyssa的real-part-rectangular.
在这样地案例中,我们使用contents来抽取不带标签的具体内容部分。
把它发给需要这个内容的平面坐标程序或者是极坐标程序。

(define (real-part z)
        (cond ((rectangular? z)
               (real-part-rectangular (contents z)))
              ((polar? z)
                (real-part-polar (contents z)))
              (else (error "Unknown type --REAL-PART" z))
        )
)

(define (imag-part z)
        (cond ((rectangular? z)
               (imag-part-rectangular (contents z)))
              ((polar? z)
                (imag-part-polar (contents z)))
              (else (error "Unknown type --IMAG-PART" z))
        )
)

(define (magnitude z)
        (cond ((rectangular? z)
               (magnitude-rectangular (contents z)))
              ((polar? z)
                (magnitude-polar (contents z)))
              (else (error "Unknown type --MAGNITUDE" z))
        )
)

(define (angle z)
        (cond ((rectangular? z)
               (angle-rectangular (contents z)))
              ((polar? z)
                (angle-polar (contents z)))
              (else (error "Unknown type --ANGLE" z))
        )
)

为了实现复数的算术操作,我们能够使用与2.4.1部分中相同的程序add-complex,
sub-complex,mul-complex,div-complex,因为它们调用的选择子是通用的,
所以在任何一个表示方式下都能正常工作。例如 add-complex的程序仍然是如下的样子。
(define (add-complex z1 z2)
     (make-from-real-imag (+ (real-part z1) (real-part z2)) (+ (imag-part z1) (imag-part z2)))
)

最后,我们必须选择使用哪种表示方式来组装复数。一个合适的选择是当我们有实部和虚部时组装平面坐标的复数,
当我们有长度和角度的时候,组装极坐标的复数。

(define (make-from-real-imag x y)
     (make-from-real-imag-rectangular x y))
(define (make-from-mag-ang r a)
     (make-from-mag-ang-polar r a))

               使用复数的程序
      __________________________________________________   
------|add-complex sub-complex mul-complex div-complex |------------
      --------------------------------------------------
               复数算术程序包
            ______________________
------------|real-part imag-part |------------------------
            |magnitude angle     |
            ----------------------
      平面坐标表示    |  极坐标表示
                      |
______________________|______________________________________                
          列表结构和原生的机器算术

图2.21 通用的复数算术系统的结构

现阶段的复数系统已经有了如图2.21所示的结构。系统已经被拆分成三个相互独立的部分。
复数算术操作部分,极坐标表示法的实现和平面坐标表示法的实现部分。极坐标和平面坐标的表示法
部分,已经被Ben和Alyssa分别地单独实现。 通过抽象的组装子和选择子,第三个程序员来实现
复数操作,能够使用这两种表示法。

因为任何一个数据对象都被标签标识了它的类型,选择子操作数据以一种通用的方式进行。
也就是任何一个选择子被定义有一个行为。这个行为是依赖于它操作的数据的具体类型。
注意一下分离单独的表示法的通用的机制,在给定的表示法实现中,(例如 alyssa的极坐标程序包)
一个复数是一个没有类型标签的数对(长度,角度)
当一个通用的选择子操作一个 polar 类型的数据时,它抽出类型标签,把内容发到alyssa的代码中。
反过来,alyssa为了通用的目的在组装一个复数时,她为类型打一个标签,为了它这个复数能够被
高层次的程序合适地识别出来。对于数据对象的抽取与添加标签的原则,被层层传递下去,成为了一种重要的组织策略。
正如我们在2.5部分中将看到的那样。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值