Решение упражнения 2.82 из SICP
Сначала приведем обобщенную реализацию apply-generic, которая реализует приведение типов для операций с любым количеством аргументов, а не только для бинарных операций. При вызове apply-generic для некоторого набора аргументов будем пытаться найти процедуру, работающую с данными типами аргументов. Если же такой процедуры нет в таблице типов, будем последовательно приводить все аргументы к типу первого, затем к типу второго и т.д. и пытаться найти процедуру, работающую с такими типами. Если нам это удается, то вызываем эту процедуру с приведенными аргументами.
Реализация этой идеи в виде процедур приводится ниже:
(define (coerce arg type)
(if (eq? (type-tag arg) type)
arg
(let ((arg->type (get-coercion (type-tag arg) type)))
(if arg->type
(arg->type arg)
false))))
(define (coerce-all args type)
(if (null? args)
'()
(let ((first (coerce (car args) type))
(rest (coerce-all (cdr args) type)))
(if (and first rest)
(cons first rest)
false))))
(define (apply-with-coercion op args types)
(if (null? types)
(error "Нет метода для этих типов"
(list op types))
(let ((type (car types)))
(let ((coerced-args (coerce-all args type)))
(if coerced-args
(apply apply-generic (cons op coerced-args))
(apply-with-coercion op args (cdr types)))))))
(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))
(apply-with-coercion op args type-tags)))))
Процедура apply-generic использует процедуру apply-with-coercion, которая пытается найти подходящую операцию для аргументов, приведенных к одному типу из набора. Та в свою очередь использует coerce-all, которая приводит все аргументы к заданному типу (либо возвращает ложь, если это невозможно). coerce-all использует процедуру coerce для приведения одного аргумента к заданному типу.
Однако и разработанная обобщенная процедура приведения типов не всесильна. Если у нас в системе возможны операции со смешанными типами, то мы можем не найти подходящий набор приведений, хотя он и существует. Рассмотрим пример с целыми числами, рациональными и комплексными. Предположим, что операция возведения в степень задана для комплексного и рационального числа (то есть мы умеем возводить комплексное число в рациональную степень). Мы также умеем приводить целые числа к рациональным, а рациональные к комплексным. Тем не менее, если мы вызовем обобщенную операцию возведения в степень для комплексного числа и целого, мы не получим ожидаемого результата.
Почему так происходит? Потому что apply-generic сначала попытается найти процедуру возведения в степень, заданную для комплексного и целого аргументов. Такой операции в таблице типов нет, поэтому будут предприняты попытки приведения типов. Сначала попытаемся привести тип второго аргумента к типу первого. Предположим, это удается, так как целое в принципе можно привести к комплексному. Однако операции возведения в степень для двух комплексных чисел у нас в системе нет. Теперь попытаемся привести первый аргумент к типу второго. Это не получается, так как комплексное число в общем случае не приводится к целому.
В то же время, мы могли бы привести второй целочисленный аргумент к рациональному типу и найти имеющуюся операцию.
Comments
Comment from Sergey Khenkin
Date: February 29, 2008, 11:12 pm
Даже если рассматривать только операции над аргументами одного типа, возможны случаи, когда обобщенная операция определена на типе, к которому можно привести типы каждого из передаваемых аргументов, однако не определена для любых комбинаций типов аргументов.
Например, представим себе, что сложение определено только для комплексных чисел, а вызывается для целого и рационального аргументов.
В этом случае имеющиеся процедуры также не смогут найти и использовать правильный вариант приведения типов.
Comment from gorilych
Date: August 17, 2008, 11:14 am
(if (null? types)
(error "Нет метода для этих типов"
(list op types))
список типов всегда пуст в ошибке. ошибка в выведении ошибки….
Write a comment