2.5 Systems with Generic Operations - CloneableX/SICP-learning GitHub Wiki

在前面的章节,我们已经了解处理数据对象多种表现形式的技术。其中的关键是将数据运算方法与对应的类型关联起来,然后通过通用接口在系统中使用。本章目前要介绍的不仅是不同数据对象表现方式的通用操作,还有对于多种类型参数的通用运算。之前已经展示了几种不同的算法代码包,有编程语言自带的基础算法(+、-、*、/),有有理数算法(add-rat sub-rat mul-rat div-rat),以及复数算法。在本节接下来的内容中将使用数据导向编程技术构建一个算法包,它包含了之前介绍的三种算法。

下图展示了通用算法包的系统结构,需要注意的是其中的抽象屏障。从具体被应用的数据来看,add 只是一个单纯运算参数的程式。但 add 的实质是基础算法、有理数算法和复数算法应用于参数的通用接口,任何独立的算法包(比如复数包)都是通过为不同表现方式设计的通用程式访问的,不过整个系统被设计为可增量的,于是这些算法包可以被分别设计开发,最后通过通用算法将它们结合至系统中即可。

Generic arithmetic system

通用算术运算

通用算术运算的设计工作与复数运算的设计类似。比如,此系统应该存在通用加法程式 add,当应用于原始数字时使用 +,应用于有理数时使用 add-rat,应用于复数时使用 add-complex。此时可以使用与复数通用选择器设计时一样的策略,于是需要为每种数据添加类型标识,然后通用程式根据类型标识为参数调用对应的程式。

通用算法程式实现如下:

(define (add x y) (apply-generic 'add x y))
(define (sub x y) (apply-generic 'sub x y))
(define (mul x y) (apply-generic 'mul x y))
(define (div x y) (apply-generic 'div x y))

接着先安装基本数字类型的包,可以为其添加标识 scheme-number。在此包中的算术运算就是基础算术程式,所以也就不需要重新定义算法程式以及处理去除标识后的数据。又因为这些运算需要接收两个参数,所以算法在对应表中的标识是 (scheme-number scheme-nubmer)

(define (install-scheme-number-package)
  (define (tag x) (attach-tag 'scheme-number x))
  (put 'add '(scheme-number scheme-number)
    (lambda (x y) (tag (+ x y))))
  (put 'sub '(scheme-number scheme-number)
    (lambda (x y) (tag (- x y))))
  (put 'mul '(scheme-number scheme-number)
    (lambda (x y) (tag (* x y))))
  (put 'div '(scheme-number scheme-number)
    (lambda (x y) (tag (/ x y))))
  (put 'make 'scheme-number (lambda (x) (tag x)))
  'done)

Scheme 基础数据包的使用者通用下列程式创建基础数字类型数据:

(define (make-scheme-number n)
  ((get 'make 'scheme-number) n))

目前已经有了通用算术系统的基本框架,接下来是添加其他类型的数据。按顺序接着加入有理数算术包,除了需要开发有理数相关的包外并不需要修改任何系统原有代码,此时拥有增量特性的系统优势彰显无疑。

(define (install-rational-package)
  ;; internal procedures
  (define (numer x) (car x))
  (define (denom x) (cdr x))
  (define (make-rat n d)
    (let ((g (gcd n d)))
      (cons (/ n g) (/ d g))))
  (define (add-rat x y)
    (make-rat (+ (* (numer x) (denom y))
                 (* (numer y) (denom x)))
              (* (denom x) (denom y))))
  (define (sub-rat x y)
    (make-rat (- (* (numer x) (denom y))
                 (* (numer y) (denom x)))
              (* (denom x) (denom y))))
  (define (mul-rat x y)
    (make-rat (* (numer x) (numer y))
              (* (denom x) (denom y))))
  (define (div-rat x y)
    (make-rat (* (numer x) (denom y))
              (* (denom x) (numer y))))

  ;; interface to rest of the system
  (define (tag x) (attach-tag 'rational x))
  (put 'add '(rational rational)
       (lambda (x y) (tag (add-rat x y))))
  (put 'sub '(rational rational)
       (lambda (x y) (tag (sub-rat x y))))
  (put 'mul '(rational rational)
       (lambda (x y) (tag (mul-rat x y))))
  (put 'div '(rational rational)
       (lambda (x y) (tag (div-rat x y))))
  (put 'make 'rational
       (lambda (n d) (tag (make-rat n d))))
  'done)

(define (make-rational n d)
  ((get 'make 'rational) n d))

安装复数算术包也是类似的方式,不过标识需要使用 complex。除了复数的算术运算程式之外,还需要将复数算术包中的 make-from-real-imagmake-from-mag-ang 提取出来加入类型与运算关系表中。具体实现如下:

(define (install-complex-package)
  ;; imported procedures from rectangular and polar packages
  (define (make-from-real-imag x y)
    ((get 'make-from-real-imag 'rectangular) x y))
  (define (make-from-mag-ang r a)
    ((get 'make-from-mag-ang 'polar) r a))

  ;; internal procedures
  (define (add-complex z1 z2)
    (make-from-real-imag (+ (real-part z1) (real-part z2))
                         (+ (imag-part z1) (imag-part z2))))
  (define (sub-complex z1 z2)
    (make-from-real-imag (- (real-part z1) (real-part z2))
                         (- (imag-part z1) (imag-part z2))))
  (define (mul-complex z1 z2)
    (make-from-mag-ang (* (magnitude z1) (magnitude z2))
                       (+ (angle z1) (angle z2))))
  (define (div-complex z1 z2)
    (make-from-mag-ang (/ (magnitude z1) (magnitude z2))
                       (- (angle z1) (angle z2))))

  ;; interface to rest of the system
  (define (tag z) (attach-tag 'complex z))
  (put 'add '(complex complex)
    (lambda (z1 z2) (tag (add-complex z1 z2))))
  (put 'sub '(complex complex)
    (lambda (z1 z2) (tag (sub-complex z1 z2))))
  (put 'mul '(complex complex)
    (lambda (z1 z2) (tag (mul-complex z1 z2))))
  (put 'div '(complex complex)
    (lambda (z1 z2) (tag (div-complex z1 z2))))
  (put 'make-from-real-imag 'complex
    (lambda (x y) (tag (make-from-real-imag x y))))
  (put 'make-from-mag-ang 'complex
    (lambda (r a) (tag (make-from-mag-ang r a))))
  'done)

在复数包外部依然可以通过矩形方式或极坐标方式创建复数,不过需要如上述程式一样将矩形算法包和极坐标算法包中的创建复数程式引入复数算法包中使用,在当前系统中创建复数的程式如下:

(define (make-complex-from-real-imag x y)
  ((get 'make-from-real-imag 'complex) x y))
(define (make-complex-from-mag-ang r a)
  ((get 'make-from-mag-ang 'complex) r a))

于是复数形成了两级标识系统。3+4i 这个通过矩形形式表示的复数结构如下图。较外部的标识 complex 用于识别并调用复数算法包中算术运算,而在复数包中,另一个标识 rectangular 用于识别并调用矩形形式算法包中程式。在一些大型系统中可能存在更多层的标识,它们引导着通用方法对下级接口的调用。就如同数据对象向传递的方向一样,外部标识引导通用算法将其中的内容应用于对应算法包,而下一级的标识用于接下来的分发。

Representation of 3+4i in rectangular form

在前面的算法包中,我们将基础数字类型、有理数和复数的算术运算通用化。虽然它们的内部实现各有不同,但算术运算的调用方式已经完成了统一。

整合不同类型数据

之前我们创建了一个统一的算术运算系统,它涵盖了基础数字类型、复数、有理数及其他自定义的数据类型的算术运算。每种类型的数据只能单独运算,也就是说,只能两个基础数字相加或两个复数相加,目前还没有处理跨数据类型的运算,比如使一个复数与一个基础数字相加。在整个系统的开发中,我们竭尽全力地引入屏障以保证算法包之间不用互相了解,能够独立开发。不过也存在一些可控的跨类型运算技术,它们并不会破坏模块之间的屏障。

其中一种处理跨类型运算的方式是为每种需要运算的类型组合设计一个有效的程式。例如,如果要对复数包进行扩展并为其添加计算复数和整数加法的程式,可以在对应表中添加标识为 (complex scheme-number)

;; to be included in the complex package
(define (add-complex-to-schemenum z x)
  (make-from-real-imag (+ (real-part z) x) (imag-part z)))
(put 'add '(complex scheme-number)
     (lambda (z x) (tag (add-complex-to-shcemenum z x))))

虽然代码如预期运行,但开发工作变得十分繁琐。如果向系统再引入一种新类型的数据,除了开发新增类型数据的算法包外,还需要在其他类型的算法包中添加相应的跨类型算法。这种修改不仅需要编写比定义新增类型本身更多的代码,而且会破坏系统增量插入算法包的能力,或者说增加了单个算法包开发者需要兼顾其他类型的复杂程度。例如,在上面的例子中,看起来复数与整数的加法理因被添加在复数算法包中,但事实上它可以被添加到复数算法包,也可以被添加至有理数算法包,甚至是其他相关的第三方包中。所以在设计算法包和跨算法包的系统时确定算法包之间的责任是一项重要的工作。

强制转换

在完全不相关的操作作用于完全不相关的类型时,虽然这种做法繁琐但却是目前最好的结果了。幸运的是,我们还可以依靠系统的可增量结构做到更好。通常不同的数据类型是完全不相关的,但可以将其中一种类型的数据看作另一种类型的数据,这种方式称为 强制转换(coercion)。例如,现在打算组合复数和整数的算法,于是可以将整数视作复数,不过虚数部分为 0,结果将问题转换为了复数的计算,此时直接使用复数包中的对应算法即可。

通常要实现强制转换需要设计相应的程式,将一种类型的对象转换为完全相等的另一种对象。下面是对整数转换为复数的实现:

(define (scheme-number->complex n)
  (make-complex-from-real-imag (contents n) 0))

可以将强制转换程式注册在专门的转换程式表中:

(put-coercion 'scheme-number 'complex scheme-number->complex)

上述程式是建立在已经存在 put-coercionget-coercion 程式的假设之上。一般情况下此表中的一些栏位是空的,因为并不是所有的类型都可以互相转换。例如,复数无法转换为整数,所以表中不会存在 complex->scheme-number 程式。

一旦强制转换程式表注册成功,就可以对 apply-generic 修改,使跨类型的算法统一处理。当需要应用一个算法时,需要检测参数是否符合算法类型表中定义的参数类型。如果对应,则调用对应程式;否则需要进行强制转换。目前只需要考虑存在两个参数的简单情况。接着检查强制转换表中是否有对应第一个参数类型转换为第二个参数类型的方法,如果存在就将参数转换类型后应用于对应程式,否则寻找是否有第二个参数类型转换为第一个参数类型的方式。最后,如果两种情况都不存在对应的转换方式,就放弃此次运算。下面是具体实现:

(define (apply-generic op . args)
  (let ((type-tags (map type-tag args)))
    (let ((proc (get op type-tags)))
      (if proc
        (apply proc (map contents args))
        (if (= (length args) 2)
            (let ((type1 (car type-tags))
                  (type2 (cadr type-tags))
                  (a1 (car args))
                  (a2 (cadr args)))
              (let ((t1->t2 (get-coercion type1 type2))
                    (t2->t1 (get-coercion type2 type1)))
                (cond (t1->t2
                       (apply-generic op (t1->t2 a1) a2))
                      (t2->t1
                       (apply-generic op a1 (t2->t1 a2)))
                      (else (error "No method for these types"
                                   (list op type-tags))))))
            (error "No method for these types"
                   (list op type-tags)))))))

强制转换方法对比直接定义对应跨类型的程式有许多优势。尽管还是需要编写相关类型的转换程式,但每一对类型只需要编写一个程式,而不需要定义一组运算程式。并且强制转换的方式将类型的转换处理放置在类型上,不需要在具体运算程式中。

另一方面,目前的类型转换还不够通用。因为即使两种被计算的数据类型之间不可转换,仍然可以通过转换为第三种类型进行计算。为了处理这种复杂情况,并保证程序的模块化,需要构建一个能够进一步处理类型间关系结构的系统,也就是接下来要讨论的。

类型层次结构

前面讨论的强制类型转换依赖于两种类型之间的自然关系,不过通常需要更加全局性的结构维护不同类型的关系。例如,假设在构建通用算法系统时,整数可以被看作有理数的特殊形势,而有理数是一种实数,实数又可以看作一种特殊的复数。这种情况可以称为 类型层次(hierarchy of types),其中整数是有理数的 子类型(subtype)(也就是说任何能够应用于有理数的算法都可以应用于整数)。反过来说,有理数是整数的 超类型(supertype),目前我们所讨论的类型体系比较特殊,其中的每种类型最多只有一个超类型和最多只有一个子类型,这种结构被称为 塔型体系(tower),具体构造如下图。

A tower of types

当出现塔型结构时,向整个体系中新增类型将变得极其简单,只需要确定新增类型的超类和子类即可。例如,如果希望增加一个整数向复数的转换,并不需要定义转换程式 integer->complex,只需要定义整数转换为有理数的方法,然后定义有理数转换为实数的方法,接着定义实数转换为复数的方法即可。最终通用算法系统按类型体系结构逐步将整数转换为复数,并应用对应的复数算法。

接下来需要重新设计 apply-generic 程式。对于每种类型,需要提供 raise 程式,它用于抛出当前类型所有体系中层级的对象。当系统需要将此对象转换为其他类型时,它将不断抛出更低层级的类型,直到所有对象都处于同一体系层级为止。

塔型结构的另一优势是每个类型都可以继承超类的所有算法。例如,按其他实现方式,如果不在整数中提供获取其实数部分的方法,即使存在整数是复数子类的事实也无法通过 real-part 提取其实数部分。不过在塔型结构中,可以通过改进 apply-generic 程式实现统一的类型算法继承。如果某个应用的算法在提供类型中没有定义,可以将对象抛向它的超类再次尝试应用对应算法。也就是说这些参数在逐级爬塔,直到出现期望的方法或到达塔顶。

塔型结构相比于其他类型体系的优势还在于,它能够提供一种简便的方式,使数据对象降低层级实现最简的表现方式。例如,如果将 2+3i4-3i 相加,最佳结果应该是整数 6 而不是 6+0i

类型体系的不足

如果数据类型能够自动按塔型结构组织,将大大简化处理不同类型的通用算法的困难。但不幸的是,这种情况并不会发生,下图展示了一种更加复杂的混合类型分布情况,其中不同类型的转换关系呈几何形状。通常情况下,一个类型可能存在多个子类型,如同下图中的子类型一样,构成三角形或四边形这样的多边形结构。而多超类的问题更加棘手,这意味着在体系结构中存在多种方式可以升级类型。也就是说通用算法为了保证升级至正确的类型,还需要查验整个类型网络,当一个类型拥有多个子类型时也存在类似的问题,不过是发生在降级类型时。在设计大型系统时,既要处理大量的类型关系,又要保持模块化是十分困难的事情,这也是目前需要重点研究的领域。

Relations among types of geometric figures

示例:符号代数

符号代数式是一个复杂的问题,它通常出现于大型系统中用于证明大量难道。通常,一个代数表达式可以视作一个体系结构,相当于将运算符树应用于数据。现在可以从一组基础对象开始构建代数表达式,比如常量、变量与加法、乘法组成的代数表达式。与其他编程语言一样,需要将这些简单元素组合并构建起对应的抽象。最经典的抽象方式是符号代数式,类似线性组合、多项式、有理函数或者三角函数。上述这些例子都可以被视作复合类型,这种抽象对于表达式的直观操作十分有用。例如,可以将表达式 表述为关于 x 的多项式并且以整数和关于 y 的三角函数为系数。

不过我们并不打算开发一个完整的代数式操作系统。这样的系统需要进行极复杂的编程工作,以及深入的代数知识和复杂的算法。目前只是打算开发其中较为简单但极为重要的部分,也就是多项式算法部分。在一些过程中将讲解几种设计师面对此类系统需要处理的问题,以及如何通过数据抽象和通用算法进行解决。

多项式算法

在设计多项式算法系统时的第一要务是确定多项式的具体细节。多项式常规的定义是与某些变量(也就是多项式中的不定项)相关,为了简化问题,先限制多项式只有一个变量(也就是单变量多项式)。于是多项式每个单元可以是系数与变量 n 次幂的和或系数与变量 n 次幂的积,多项式中的系数是与多项式变量不相关的代数式。例如, 是一个关于 x 的多项式,而 也是关于 x 的多项式,不过系数是 y 的多项式而已。

尽管已经避开了一些棘手问题,但还有个问题需要思考。是否多项式都如同 一样?你可能会这样回答,如果只考虑纯数学函数的多项式答案是肯定的,如果多项式是符号形式则答案是否定的。第二个表达式等同于关于 y 的多项式作为系数的关于 x 的多项式,这种情况当前的系统需要处理吗?而且,除此之外还有其他的多项式形式,例如因数的乘积形式,或者一组多次方根,或者作为多项式在指定点的数值列表。我们的代数系统将会把这些形式处理为特殊的符号形式,而不是直接依照原有的数学方式表达。

现在还需要考虑如果对多项式应用算法。在这个简单的系统中,暂时只需要考虑加法和乘法。而且,还需要保证进行运算的两个代数式拥有相同的不定项。

接着将按下列数据抽象规则设计系统。表示多项式可以通过 poly 数据结构,它能够将一个变量与一组元素结合。并且可以先假设已经存在选择器 variableterm-list,它们分别可以提取 poly 中的变量和一组元素。其中的变量是标识,可以通过 same-variable? 比较两个变量是否相同。下列程式定义了关于 poly 的加法和乘法:

(define (add-poly p1 p2)
  (if (same-variable? (variable p1) (variable p2))
      (make-poly (variable p1)
                 (add-terms (term-list p1) (term-list p2)))
      (error "Polys not in same var: ADD-POLY" (list p1 p2))))

(define (mul-poly p1 p2)
  (if (same-variable? (variable p1) (variable p2))
      (make-poly (variable p1)
                 (mul-terms (term-list p1) (term-list p2)))
      (error "Polys not in same var: MUL-POLY" (list p1 p2))))

为了将多项式加入通用算法系统,需要提供多项式的类型标识。目前以 polynomial 为多项式的类型标识,并将相应的方法注册至对应表中。下面是多项式的安装包:

(define (install-polynomial-package)
  ;; internal procedures
  ;; representation of poly
  (define (make-poly variable term-list) (cons variable term-list))
  (define (variable p) (car p))
  (define (term-list p) (cdr p))
  ⟨procedures same-variable? and variable? from section 2.3.2⟩
  ;; representation of terms and term lists
  ⟨procedures adjoin-term ...coeff from text below⟩
  (define (add-poly p1 p2) ...)
  ⟨procedures used by add-poly⟩
  (define (mul-poly p1 p2) ...)
  ⟨procedures used by mul-poly⟩
  ;; interface to rest of the system
  (define (tag p) (attach-tag 'polynomial p))
  (put 'add '(polynomial polynomial)
    (lambda (p1 p2) (tag (add-poly p1 p2))))
  (put 'mul '(polynomial polynomial)
    (lambda (p1 p2) (tag (mul-poly p1 p2))))
  (put 'make 'polynomial
    (lambda (var terms) (tag (make-poly var terms))))
  'done)

多项式的加法需要逐项运算,然后合并相同阶的项(例如相同次幂的不定式)。接着生成一个同阶的项,不过需要系数为原先两个项的系数之和。如果某项没有同阶的项,可以直接将其合并至结果多项式中。

为了维护项列表,需要假设已经存在构造器 the-empty-termlist 用于返回项的空列表,以及另一个构造器 adjoin-term 将新的项拼接于项列表后,以及 empty-termlist? 用于判断是否空的项列表,最后还有一个选择器 rest-terms 可以获取余下的项。除此之外,还需假设存在 make-term 构造器将系数和阶结合,以及选择器 ordercoeff 获取阶数和系数。这些程式提供了关于项和项列表的数据抽象。

下面是构造项列表和多项式的相加程式:

(define (add-terms L1 L2)
  (cond ((empty-termlist? L1) L2)
        ((empty-termlist? L2) L1)
        (else
         (let ((t1 (first-term L1))
               (t2 (first-term L2)))
           (cond ((> (order t1) (order t2))
                  (adjoin-term
                    t1 (add-terms (rest-terms L1) L2)))
                 ((< (order t1) (order t2))
                  (adjoin-term
                    t2 (add-terms L1 (rest-terms L2))))
                 (else
                  (adjoin-term
                    (make-term (order t1)
                               (add (coeff t1) (coeff t2)))
                    (add-terms (rest-terms L1)
                               (rest-terms L2)))))))))

其中的关键点在于计算系数加法时使用的是通用加法程式 add,这有着极其重要的意义。

为了计算两个项列表的乘积,需要将列表中与另一组项列表中的所有项相乘,可以通过使用 mul-term-by-all-terms 进行此运算,然后将运算结果通过相加的方式整合。两项相乘地需要将变量的幂数相加,同时系数相乘。

(define (mul-terms L1 L2)
  (if (empty-termlist? L1)
      (the-empty-termlist)
      (add-terms (mul-term-by-all-terms (first-term L1) L2)
                 (mul-terms (rest-terms L1) L2))))
(define (mul-term-by-all-terms t1 L)
  (if (empty-termlist? L)
      (the-empty-termlist)
      (let ((t2 (first-term L)))
        (adjoin-term
         (make-term (+ (order t1) (order t2))
                    (mul (coeff t1) (coeff t2)))
         (mul-term-by-all-terms t1 (rest-terms L))))))

以上就是关于多项式的加法和乘法实现。要注意的是,当使用通用程式 addmul 对项进行运算时,多项式包就能够自动处理不同类型的系数。如果按前一节讨论的方式将多项式加入强制类型转换体系,通用算法系统也可以处理以多项式为系数的多项式了。比如,

因为在通用算法系统中加入了多项式的加法和乘法程式 add-polymul-poly,所以 addmul 通用算法会对多项式类型的数据对象应用相应的程式。

其中的原因是当系统在运算两个系数时,会委派给 addmul 程式。当系数是关于另一个变量的多项式时,就会通过 add-polymul-poly 进行运算。这种情况属于一种数据导向的递归,如同调用 mul-poly 结果会递归调用 mul-poly 计算系数结果一样。如果系数的系数还是多项式,数据导向会在系统中确认下一级的递归调用,并以此类推,通过数据确定更多的层级。

表示项列表

最后还需要实现关于项列表的表示方式。实际上,每一个项都是一组系数与变量的次幂组成,所以任何表示集合的方式都可以用于表示项。另一方面,add-termsmul-terms 程式总是按照次幂从高至低的顺序访问项列表,所以使用有序列表表示最合适。

那么接下来如何组建列表呢?其中的一个考虑是需要了解多项式的紧密程式。如果一个多项式的多次幂项都拥有非零系数,则该多项式是紧密的,如果存在多个系数为零的项则该多项式是稀疏的。比如表达式 A 是紧密的多项式,而表达式 B 是稀疏的多项式。

对于紧密的多项式,使用系数列表表示会更加高效。比如,上述表达式 A 可以表示为 (1 2 0 3 -2 -5),而系数对应的每一项变量次幂为当前系数所处子列表的长度减 1。不过这种表达方式对于表达式 B 就是极低效的,因为形成的系数列表中有大量的 0,非零值项只有零星的几个。这种稀疏的多项式更适合使用非零项作为列表表示,也就是每一项的系数和次幂都作为元素包含在列表中。按此方案执行,多项式 B 可以表示为 ((100 1) (2 2) (0 1))。其实大多数多项式都是稀疏形的,所以我们使用后面一种方案。首先假设多项列表是以每一项为元素的列表,然后再按高阶向低阶的排序方式排列。一旦确定实现方案,相应的构造器和选择器便可以按如下方式实现:

(define (adjoin-term term term-list)
  (if (=zero? (coeff term))
      term-list
      (cons term term-list)))
(define (the-empty-termlist) '())
(define (first-term term-list) (car term-list))
(define (rest-terms term-list) (cdr term-list))
(define (empty-termlist? term-list) (null? term-list))
(define (make-term order coeff) (list order coeff))
(define (order term) (car term))
(define (coeff term) (cadr term))

多项式的用户可以使用下列程式创建多项式:

(define (make-polynomial var terms)
  ((get 'make 'polynomial) var terms))

符号代数式的类型结构

多项式的例子探讨了如何将多种不同类型的对象组成同一类型的复杂对象。这种情况并不会对通用运算的编写造成过度的困难,只需要将对应运算对象的算法安装至通用算法中即可。事实上,目前实现的多项式是一种递归数据抽象。只要通过通用程式和数据导向的方式便能完美处理此类问题。

另一方面,多项式系统的数据类型并不能自主形成塔型结构,因为多项式的系数也可能是另一个多项式。作为系数的多项式与原本的多项式本身并没有天然的上下级关系,甚至有时候需要将这两组多项式相加。目前唯一可能的方案是将一个多项式通过扩展和重排的方式转换为与另一个多项式拥有相同变量的多项式。通过变量排序,并将任意多项式转换为规范形式,其中高权重的变量作为变量,而低权重的变量混入系数中,按这种方式缔造了一个类塔型结构。除了某些时候这种转换会将一个不必要的多项式展开,导致整个多项式难以阅读和降低执行效率外,这个策略运转得很成功。塔型策略对于这类问题并不合适,或者说对于任何用户能够自主发明新类型,并且新类型能够通过不同形式将原有类型结合的情况都不合适,比如说三角函数、幂积数和积分。

在设计大型代数式系统时控制强制类型转换就是一个严肃的问题。此类系统的复杂性多数来自于不同类型间的关系。而且严格说我们并没有完全掌握类型转换,甚至可以说我们尚未完全掌握一种数据类型的概念。不过,目前的知识足够我们创建有用的结构,模块化的思想也能指导我们设计出良好的大型系统。