第九章 结构

自然分组的数据被称为结构。我们可以使用Scheme提供的复合数据结构如向量和列表来表示一种“结构”。例如:我们正在处理与树木相关的一组数据。数据(或者叫字段field)中的单个元素包括:高度,周长,年龄,树叶形状和树叶颜色共5个字段。这样的数据可以表示为5元向量。这些字段可以利用vector-ref访问,或使用vector-set!修改。尽管如此,我们仍然不希望记忆向量索引编号与字段的对应关系,这是一个费力且容易出错的事情,尤其是随着时间的流逝,一些字段被加进来,而另一些字段会被删掉。

因此我们使用Scheme的宏defstruct去定义一个结构,基本上你可以把它当作一种向量,不过它提供了很多方法诸如创建结构实例、访问或修改它的字段等等。因此,我们的树结构应这样定义:

  1. (defstruct tree height girth age leaf-shape leaf-color)

这样它自动生成了一个名为make-tree的构造过程,以及每个字段的访问方法,命名为tree.heighttree.girth等等。构造方法的使用方法如下:

  1. (define coconut
  2. (make-tree 'height 30
  3. 'leaf-shape 'frond
  4. 'age 5))

这个构造函数的参数以成对的形式出现,字段名后面紧跟着其初始值。这些字段能以任意顺序出现,或者不出现——如果字段的值没有定义的话。

访问过程的调用如下所示:

  1. (tree.height coconut) => 30
  2. (tree.leaf-shape coconut) => frond
  3. (tree.girth coconut) => <undefined>

tree.girth存取程序返回一个未定义的值,因为我们没有为coconut这个tree结构指定girth的值。

修改过程的调用如下所示:

  1. (set!tree.height coconut 40)
  2. (set!tree.girth coconut 10)

如果我们现在重新调用访问过程去访问这些字段,我们会得到新的值:

  1. (tree.height coconut) => 40
  2. (tree.girth coconut) => 10

9.1 默认初始化

我们可以在定义结构时进行一些初始化的设置,而不是在每个实例中都进行初始化。因此,我们假定leaf-shapeleaf-color在默认情况下分别为frondgreen。我们可以在调用make-tree时通过显式的初始化来覆盖掉这些默认值,或者在创建一个结构实例后使用上面提到的字段修改过程:

  1. (defstruct tree height girth age
  2. (leaf-shape 'frond)
  3. (leaf-color 'green))
  4. (define palm (make-tree 'height 60))
  5. (tree.height palm)
  6. => 60
  7. (tree.leaf-shape palm)
  8. => frond
  9. (define plantain
  10. (make-tree 'height 7
  11. 'leaf-shape 'sheet))
  12. (tree.height plantain)
  13. => 7
  14. (tree.leaf-shape plantain)
  15. => sheet
  16. (tree.leaf-color plantain)
  17. => green

9.2 defstruct定义

defstruct的定义如下:

  1. (define-macro defstruct
  2. (lambda (s . ff)
  3. (let ((s-s (symbol->string s)) (n (length ff)))
  4. (let* ((n+1 (+ n 1))
  5. (vv (make-vector n+1)))
  6. (let loop ((i 1) (ff ff))
  7. (if (<= i n)
  8. (let ((f (car ff)))
  9. (vector-set! vv i
  10. (if (pair? f) (cadr f) '(if #f #f)))
  11. (loop (+ i 1) (cdr ff)))))
  12. (let ((ff (map (lambda (f) (if (pair? f) (car f) f))
  13. ff)))
  14. `(begin
  15. (define ,(string->symbol
  16. (string-append "make-" s-s))
  17. (lambda fvfv
  18. (let ((st (make-vector ,n+1)) (ff ',ff))
  19. (vector-set! st 0 ',s)
  20. ,@(let loop ((i 1) (r '()))
  21. (if (>= i n+1) r
  22. (loop (+ i 1)
  23. (cons `(vector-set! st ,i
  24. ,(vector-ref vv i))
  25. r))))
  26. (let loop ((fvfv fvfv))
  27. (if (not (null? fvfv))
  28. (begin
  29. (vector-set! st
  30. (+ (list-position (car fvfv) ff)
  31. 1)
  32. (cadr fvfv))
  33. (loop (cddr fvfv)))))
  34. st)))
  35. ,@(let loop ((i 1) (procs '()))
  36. (if (>= i n+1) procs
  37. (loop (+ i 1)
  38. (let ((f (symbol->string
  39. (list-ref ff (- i 1)))))
  40. (cons
  41. `(define ,(string->symbol
  42. (string-append
  43. s-s "." f))
  44. (lambda (x) (vector-ref x ,i)))
  45. (cons
  46. `(define ,(string->symbol
  47. (string-append
  48. "set!" s-s "." f))
  49. (lambda (x v)
  50. (vector-set! x ,i v)))
  51. procs))))))
  52. (define ,(string->symbol (string-append s-s "?"))
  53. (lambda (x)
  54. (and (vector? x)
  55. (eqv? (vector-ref x 0) ',s))))))))))