Х. Абельсон, Дж. Дж. Сассман, Дж. Сассман - Структура и интерпретация компьютерных программ (1108516), страница 42
Текст из файла (страница 42)
Предположим, кпримеру, что нам хочется ввести в нашу систему комплексных чисел еще одно представление. Нам нужно будет сопоставить этому представлению тип, а затем добавить вкаждую из обобщенных процедур интерфейса по варианту для проверки на этот новыйтип и вызова селектора, соответствующего его представлению.Второй недостаток этого метода диспетчеризации состоит в том, что, хотя отдельныепредставления могут проектироваться раздельно, нам нужно гарантировать, что никакиедве процедуры во всей системе не называются одинаково.
Вот почему Бену и Лизепришлось изменить имена своих первоначальных процедур из раздела 2.4.1.Оба эти недостатка являются следствием того, что наш метод реализации обобщенных интерфейсов неаддитивен. Программист, реализующий обобщенные процедурыселекторы, должен их переделывать каждый раз, как добавляется новое представление, а авторы, создающие отдельные представления, должны изменять свой код, чтобыизбежать конфликтов имен. В каждом из этих случаев изменения, которые требуетсявнести в код, тривиальны, но их все равно нужно делать, и отсюда проистекают неудобства и ошибки.
Для системы работы с комплексными числами в ее нынешнем виде этопроблема небольшая, но попробуйте представить, что есть не два, а сотни различныхпредставлений комплексных чисел. И что есть много обобщенных селекторов, которыенадо поддерживать в интерфейсе абстрактных данных. Представьте даже, что ни одинпрограммист не знает всех интерфейсных процедур всех реализаций. Проблема эта реальна, и с ней приходится разбираться в программах вроде систем управления базамиданных большого калибра.Глава 2. Построение абстракций с помощью данных180Операцииreal-partimag-partmagnitudeangleТипыPolarreal-part-polarimag-part-polarmagnitude-polarangle-polarRectangularreal-part-rectangularimag-part-rectangularmagnitude-rectangularangle-rectangularРис.
2.22. Таблица операций в системе комплексных чисел.Нам нужен способ еще более модуляризовать устройство системы. Это позволяет метод программирования, который называется программирование, управляемое данными(data-directed programming). Чтобы понять, как работает этот метод, начнем с наблюдения: каждый раз, когда нам приходится работать с набором обобщенных операций,общих для множества различных типов, мы, в сущности, работаем с двумерной таблицей, где по одной оси расположены возможные операции, а по другой всевозможные типы.
Клеткам таблицы соответствуют процедуры, которые реализуют каждую операциюдля каждого типа ее аргумента. В системе комплексной арифметики из предыдущегораздела соответствие между именем операции, типом данных и собственно процедуройбыло размазано по условным предложениям в обобщенных процедурах интерфейса. Ноту же самую информацию можно было бы организовать в виде таблицы, как показанона рис. 2.22.Программирование, управляемое данными, — метод проектирования программ, позволяющий им напрямую работать с такого рода таблицей. Механизм, который связываеткод комплексных арифметических операций с двумя пакетами представлений, мы ранеереализовали в виде набора процедур, которые явно осуществляют диспетчеризацию потипу.
Здесь мы реализуем этот интерфейс через одну процедуру, которая ищет сочетаниеимени операции и типа аргумента в таблице, чтобы определить, какую процедуру требуется применить, а затем применяет ее к содержимому аргумента. Если мы так сделаем,то, чтобы добавить к системе пакет с новым представлением, нам не потребуется изменять существующие процедуры; понадобится только добавить новые клетки в таблицу.Чтобы реализовать этот план, предположим, что у нас есть две процедуры put иget, для манипуляции с таблицей операций и типов:• (put hопi hтипi hэлементi) вносит hэлементi в таблицу, в клетку, индексомкоторой служат операция hопi и тип hтипi.• (get hопi hтипi) ищет в таблице ячейку с индексом hопi,hтипi и возвращает еесодержимое. Если ячейки нет, get возвращает ложь.Пока что мы предположим, что get и put входят в наш язык.
В главе 3 (раздел 3.3.3)мы увидим, как реализовать эти и другие операции для работы с таблицами.Программирование, управляемое данными, в системе с комплексными числами можноиспользовать так: Бен, который разрабатывает декартово представление, пишет код вточности как он это делал сначала. Он определяет набор процедур, или пакет (package),и привязывает эти процедуры к остальной системе, добавляя в таблицу ячейки, которыесообщают системе, как работать с декартовыми числами. Это происходит при вызове2.4.
Множественные представления для абстрактных данных181следующей процедуры:(define (install-rectangular-package);; внутренние процедуры(define (real-part z) (car z))(define (imag-part z) (cdr z))(define (make-from-real-imag x y) (cons x y))(define (magnitude z)(sqrt (+ (square (real-part z))(square (imag-part z)))))(define (angle z)(atan (imag-part z) (real-part z)))(define (make-from-mag-ang r a)(cons (* r (cos a)) (* r (sin a))));; интерфейс к остальной системе(define (tag x) (attach-tag ’rectangular x))(put ’real-part ’(rectangular) real-part)(put ’imag-part ’(rectangular) imag-part)(put ’magnitude ’(rectangular) magnitude)(put ’angle ’(rectangular) angle)(put ’make-from-real-imag ’rectangular(lambda (x y) (tag (make-from-real-imag x y))))(put ’make-from-mag-ang ’rectangular(lambda (r a) (tag (make-from-mag-ang r a))))’done)Обратите внимание, что внутренние процедуры — те самые, которые Бен писал, когда он в разделе 2.4.1 работал сам по себе.
Никаких изменений, чтобы связать их состальной системой, не требуется. Более того, поскольку определения процедур содержатся внутри процедуры установки, Бену незачем беспокоиться о конфликтах имен сдругими процедурами вне декартова пакета. Чтобы связать их с остальной системой,Бен устанавливает свою процедуру real-part под именем операции real-part итипом (rectangular), и то же самое он проделывает с другими селекторами45 . Егоинтерфейс также определяет конструкторы, которые может использовать внешняя система46 .
Они совпадают с конструкторами, которые Бен определяет для себя, но вдобавокприкрепляют метку.Лизин полярный пакет устроен аналогично:(define (install-polar-package);; внутренние процедуры(define (magnitude z) (car z))(define (angle z) (cdr z))(define (make-from-mag-ang r a) (cons r a))(define (real-part z)45 Мы используем список (rectangular), а не символ rectangular, чтобы предусмотреть возможностьопераций с несколькими аргументами, не все из которых одинакового типа.46 Тип, под которым устанавливаются конструкторы, необязательно делать списком, поскольку конструкторвсегда вызывается для того, чтобы породить один объект определенного типа.Глава 2.
Построение абстракций с помощью данных182(* (magnitude z) (cos (angle z))))(define (imag-part z)(* (magnitude z) (sin (angle z))))(define (make-from-real-imag x y)(cons (sqrt (+ (square x) (square y)))(atan y x)));; интерфейс к остальной системе(define (tag x) (attach-tag ’polar x))(put ’real-part ’(polar) real-part)(put ’imag-part ’(polar) imag-part)(put ’magnitude ’(polar) magnitude)(put ’angle ’(polar) angle)(put ’make-from-real-imag ’polar(lambda (x y) (tag (make-from-real-imag x y))))(put ’make-from-mag-ang ’polar(lambda (r a) (tag (make-from-mag-ang r a))))’done)Несмотря на то, что Бен и Лиза используют свои исходные процедуры с совпадающими именами (например, real-part), эти определения теперь внутренние для различныхпроцедур (см.
раздел 1.1.8), так что никакого конфликта имен не происходит.Селекторы комплексной арифметики обращаются к таблице посредством общей процедуры-«операции» apply-generic, которая применяет обобщенную операцию к набору аргументов. Apply-generic ищет в таблице ячейку по имени операции и типамаргументов и применяет найденную процедуру, если она существует47 :(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))(error"Нет метода для этих типов -- APPLY-GENERIC"(list op type-tags))))))При помощи apply-generic можно определить обобщенные селекторы так:(define(define(define(define(real-part z) (apply-generic ’real-part z))(imag-part z) (apply-generic ’imag-part z))(magnitude z) (apply-generic ’magnitude z))(angle z) (apply-generic ’angle z))47 Apply-generic пользуется точечной записью, описанной в упражнении 2.20, поскольку различные обобщенные операции могут принимать различное число аргументов.