
- •6. Абстракции данных
- •6.1 Представление рациональных чисел
- •6.1.1 Первое внутреннее представление рациональных чисел
- •6.1.2 Второе внутреннее представление рациональных чисел
- •6.1.3 Функциональный тип данных
- •6.1.4 Третье внутреннее представление рациональных чисел
- •6.2 Представления множеств
- •6.2.1 Функции над множествами. Определения
- •6.2.2 Представление множеств списками
- •6.2.2.1 Неупорядоченные множества
- •6.2.2.2 Упорядоченные множества
- •6.2.3 Представление множеств двоичными деревьями
- •6.2.4 Представление древовидных структур
6.1.3 Функциональный тип данных
Вернёмся к вопросу о «типе данных».
Когда мы реализовывали арифметику рациональных чисел, всё, что мы о них знали, это то, что они создаются функцией make-rat из пары целых чисел, и что для любого рационального числа можно получить его числитель и знаменатель функциями числитель и знаменатель.
При этом, для любых целых чисел n и m должно выполняться следующее условие.
(числитель (make-rat
n
m))/(знаменатель
(make-rat
n
m))
=
Это единственное условие, которое накладывается на make-rat, числитель и знаменатель, и получается, что любые три функции ему удовлетворяющие могут быть использованы для осуществления рациональных чисел.
Мы можем рассматривать тип данных как некоторый набор конструкторов и селекторов вместе с определёнными условиями, которым эти функции должны удовлетворять.
6.1.4 Третье внутреннее представление рациональных чисел
Теперь рассмотрим точечную пару с функциональной точки зрения представления данных.
Единственное “условие пары”, которому должны удовлетворять операции, работающие с ними, т.е. cons, car и cdr состоит в том, что для любых объектов x и y должно выполнятся:
(car (cons x y)) = x и
(cdr (cons x y)) = y
Получается, что любая тройка функций, удовлетворяющих этому условию, может быть использоваться для создания пар. Отсюда следует, что мы можем реализовать, cons, car и cdr вообще не связывая эти функции с понятием точечной пары! И, как вывод, можно обойтись без структур данных!
Например, создать такие функции.
> (define (cons_n x y) (lambda (a) (if a x y)))
Получается, что рациональное число – это функция с определенными заранее двумя выходами, а какой из них нам нужен определяется единственным параметром при вызове.
> (define (car_n z) (z #t))
> (define (cdr_n z) (z #f))
В качестве единственного параметра z, должна выступать определённая выше – функция с заданными x и y.
Е
Например, будет так:
((cons_n
1
2)
#t) ==> 1
((cons_n
1
2)
#f) ==> 2
Наше новое «функциональное» представление пар ничем не хуже любого другого, и если мы будем оперировать только посредством функций числитель и знаменатель, мы не сможем отличить это представление от того, которое использует какие-либо другие, скажем, «настоящие» структуры данных.
> (define (make-rat n d) (cons_n n d))
> (define (числитель r) (car_n r))
> (define (знаменатель r) (cdr_n r))
> (define r1 (make-rat 1 2))
Здесь получается, что r1 внутренне представляется так:
(lambda (a) (if a 1 2))
> (define r2 (make-rat 1 3))
> (define r3 (add-rat r1 r2))
И это – правильно!
> r3
#<procedure:48:22>
> (display-rat (sub-rat r3 r2))
=9/18
> (display-rat r1)
=1/2
Единственная неприятность, проистекающая из-за того, что результатом cons_n является функция, заключается в неотвратимости созерцать название получаемого представления, т.е. новой функции, например, #<procedure:1:22>, а не само число.