Ремонт принтеров, сканнеров, факсов и остальной офисной техники


назад Оглавление вперед




[60]

не было возможности, поскольку наша система меток типов требует, чтобы каждый объект данных был снабжен меткой. Однако на самом деле все реализации Лиспа имеют систему типов, которую они используют внутри себя. Элементарные процедуры вроде symbol? или number? определяют, относится ли объект к определенному типу. Измените определения type-tag, contents и attach-tag из раздела 2.4.2 так, чтобы наша обобщенная система использовала внутреннюю систему типов Scheme. То есть, система должна работать так же, как раньше, но только обычные числа должны быть представлены просто в виде чисел языка Scheme, а не в виде пары, у которой первый элемент символ scheme-number.

Упражнение 2.79.

Определите обобщенный предикат равенства equ? , который проверяет два числа на равенство, и вставьте его в пакет обобщенной арифметики. Операция должна работать для обычных чисел, рациональных и комплексных.

Упражнение 2.80.

Определите обобщенный предикат =zero?, который проверяет, равен ли его аргумент нулю, и вставьте его в пакет обобщенной арифметики. Предикат должен работать для обычных, рациональных и комплексных чисел.

2.5.2 Сочетание данных различных типов

Мы видели, как можно построить объединенную арифметическую систему, которая охватывает обыкновенные числа, комплексные числа, рациональные числа и любые другие типы чисел, которые нам может потребоваться изобрести, но мы упустили важный момент. Операции, которые мы до сих пор определили, рассматривают различные типы данных как совершенно независимые. Таким образом, есть отдельные пакеты для сложения, например, двух обыкновенных чисел и двух комплексных чисел. Мы до сих пор не учитывали того, что имеет смысл определять операции, которые пересекают границы типов, например, сложение комплексного числа с обычным. Мы затратили немалые усилия, чтобы воздвигнуть барьеры между частями наших программ, так, чтобы их можно было разрабатывать и понимать по отдельности. Нам бы хотелось добавить операции со смешанными типами по возможности аккуратно, так, чтобы мы их могли поддерживать, не нарушая всерьез границ модулей.

Один из способов управления операциями со смешанными типами состоит в том, чтобы определить отдельную процедуру для каждого сочетания типов, для которых операция имеет смысл. Например, мы могли бы расширить пакет работы с комплексными числами и включить туда процедуру сложения комплексных чисел с обычными, занося ее в таблицу с меткой (complex scheme-number) :49

;; включается в пакет комплексных чисел (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-schemenum z x))))

49Придется к тому же написать почти такую же процедуру для типа (scheme-number complex).


Этот метод работает, но он очень громоздок. При такой системе стоимость введения нового типа не сводится к тому, чтобы построить пакет процедур для этого типа, но включает еще построение и установку процедур, осуществляющих операции со смешанными типами. Это запросто может потребовать больше кода, чем нужно, чтобы определить операции над самим типом. Кроме того, этот метод подрывает нашу способность сочетать отдельные пакеты аддитивно, или, по крайней мере, ограничивать степень, в которой реализация отдельного пакета должна принимать другие пакеты в расчет. Скажем, в вышеприведенном примере, кажется естественным, чтобы ответственность за обработку смешанных операций с обычными и комплексными числами лежала на комплексном пакете. Однако сочетание рациональных и комплексных чисел может осуществляться комплексным пакетом, рациональным пакетом, или каким-нибудь третьим, который пользуется операциями, извлеченными из этих двух. Формулировка ясных правил разделения ответственности между пакетами может стать непосильной задачей при разработке систем с многими пакетами и многими смешанными операциями.

Приведение типов

В ситуации общего вида, когда совершенно несвязанные друг с другом операции применяются к совершенно друг с другом не связанным типам, явное написание операций со смешанными типами, как бы это ни было громоздко, -все, на что мы можем рассчитывать. К счастью, обычно мы можем воспользоваться дополнительной структурой, которая часто в скрытом виде присутствует в нашей системе типов. Часто различные типы данных не совсем независимы, и каким-то образом объекты одного типа можно рассматривать как объекты другого. Такой процесс называется приведением типов (coercion). Например, если нас просят найти некоторую арифметическую комбинацию обычного числа и комплексного, то мы можем рассматривать обычное число как такое комплексное, у которого мнимая часть равна нулю. Это сводит нашу задачу к сочетанию двух комплексных чисел, а с этим может стандартным способом справиться пакет комплексной арифметики.

В общем случае мы можем реализовать эту идею, проектируя процедуры приведения типа, которые переводят объект одного типа в эквивалентный ему объект другого типа. Вот типичная процедура приведения типов, которая преобразует данное обыкновенное число в комплексное, у которого есть действительная часть, а мнимая равна нулю:

(define (scheme-number->complex n)

(make-complex-from-real-imag (contents n) 0))

Мы записываем процедуры приведения типа в специальную таблицу приведения типов, проиндексированную именами двух типов:

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

(Предполагается, что для работы с этой таблицей существуют процедуры put-coercion и get-coercion.) Как правило, часть ячеек этой таблицы будет пуста, потому что в общем случае невозможно привести произвольный объект произвольного типа ко всем остальным типам. К примеру, нет способа привести произвольное комплексное число к обыкновенному, так что в таблице не появится общая процедура complex->scheme-number.


Когда таблица приведения типов построена, мы можем работать с приведением стандартным образом, приспособив для этого процедуру apply-generic из раздела 2.4.3. Когда нас просят применить операцию, мы первым делом, как и раньше, проверяем, не определена ли уже операция для типов аргументов. Если да, мы вызываем процедуру, найденную в таблице операций и типов. Если нет, мы пробуем применить приведение типов. Для простоты мы рассматриваем только тот случай, когда аргументов два.50 Мы проверяем таблицу преобразования типов и смотрим, можно ли объект первого типа привести ко второму типу. Если да, осуществляем приведение и снова пробуем операцию. Если объекты первого типа в общем случае ко второму не приводятся, мы пробуем приведение в обратном направлении и смотрим, нет ли способа привести второй аргумент к типу первого. Наконец, если нет никакого известного способа привести один тип к другому, мы сдаемся. Вот эта процедура:

(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 ((typel (car type-tags)) (type2 (cadr type-tags)) (al (car args)) (a2 (cadr args))) (let ((tl->t2 (get-coercion typel type2)) (t2->tl (get-coercion type2 typel)))

(cond (tl->t2

(apply-generic op (tl->t2 al) a2))

(t2->tl

(apply-generic op al (t2->tl a2))) (else

(error "Нет метода для этих типов" (list op type-tags)))))) (error "Нет метода для этих типов"

(list op type-tags)))))))

Такая схема приведения типов имеет много преимуществ перед методом явного определения смешанных операций, как это описано выше. Хотя нам по-прежнему требуется писать процедуры приведения для связи типов (возможно, n2 процедур для системы с n типами), для каждой пары типов нам нужно написать только одну процедуру, а не по процедуре на каждый набор типов и каждую обобщенную операцию.51 Здесь мы рассчитываем на то, что требуемая трансформация типов зависит только от самих типов, и не зависит от операции, которую требуется применить.

50Обобщение см. в упражнении 2.82.

51 Если мы умные, мы обычно можем обойтись меньше, чем n2 процедурами приведения типа. Например, если мы знаем, как из типа 1 получить тип 2, а из типа 2 тип 3, то можно использовать это знание для преобразования из 1 в 3. Это может сильно уменьшить количество процедур, которые надо явно задавать при введении нового типа в систему. Если нам не страшно ввести в свою систему требуемый уровень изощренности, мы можем заставить ее искать по «графу» отношений между типами и автоматически порождать все процедуры приведения типов, которые можно вывести из тех, которые явно заданы.



[стр.Начало] [стр.1] [стр.2] [стр.3] [стр.4] [стр.5] [стр.6] [стр.7] [стр.8] [стр.9] [стр.10] [стр.11] [стр.12] [стр.13] [стр.14] [стр.15] [стр.16] [стр.17] [стр.18] [стр.19] [стр.20] [стр.21] [стр.22] [стр.23] [стр.24] [стр.25] [стр.26] [стр.27] [стр.28] [стр.29] [стр.30] [стр.31] [стр.32] [стр.33] [стр.34] [стр.35] [стр.36] [стр.37] [стр.38] [стр.39] [стр.40] [стр.41] [стр.42] [стр.43] [стр.44] [стр.45] [стр.46] [стр.47] [стр.48] [стр.49] [стр.50] [стр.51] [стр.52] [стр.53] [стр.54] [стр.55] [стр.56] [стр.57] [стр.58] [стр.59] [стр.60] [стр.61] [стр.62] [стр.63] [стр.64] [стр.65] [стр.66] [стр.67] [стр.68] [стр.69] [стр.70] [стр.71] [стр.72] [стр.73] [стр.74] [стр.75] [стр.76] [стр.77] [стр.78] [стр.79] [стр.80] [стр.81] [стр.82] [стр.83] [стр.84] [стр.85] [стр.86] [стр.87] [стр.88] [стр.89] [стр.90] [стр.91] [стр.92] [стр.93] [стр.94] [стр.95] [стр.96] [стр.97] [стр.98] [стр.99] [стр.100] [стр.101] [стр.102] [стр.103] [стр.104] [стр.105] [стр.106] [стр.107] [стр.108] [стр.109] [стр.110] [стр.111] [стр.112] [стр.113] [стр.114] [стр.115] [стр.116] [стр.117] [стр.118] [стр.119] [стр.120] [стр.121] [стр.122] [стр.123] [стр.124] [стр.125] [стр.126] [стр.127] [стр.128] [стр.129] [стр.130] [стр.131] [стр.132] [стр.133] [стр.134] [стр.135] [стр.136] [стр.137] [стр.138] [стр.139] [стр.140] [стр.141] [стр.142] [стр.143] [стр.144] [стр.145] [стр.146] [стр.147] [стр.148] [стр.149] [стр.150] [стр.151] [стр.152] [стр.153] [стр.154] [стр.155] [стр.156] [стр.157] [стр.158] [стр.159] [стр.160] [стр.161] [стр.162] [стр.163] [стр.164] [стр.165] [стр.166] [стр.167] [стр.168] [стр.169] [стр.170] [стр.171] [стр.172] [стр.173] [стр.174] [стр.175] [стр.176] [стр.177] [стр.178] [стр.179] [стр.180] [стр.181] [стр.182] [стр.183] [стр.184] [стр.185] [стр.186] [стр.187] [стр.188] [стр.189] [стр.190] [стр.191] [стр.192] [стр.193] [стр.194] [стр.195] [стр.196]