Решение упражнения 2.85 из SICP

5 March, 2008 (21:09) | Решения упражнений

Определение операции проецирования project похоже на определение операции raise.

Мы также вводим обобщенную операцию:

(define (project x) (apply-generic 'project x))

и добавляем в пакеты соответствующие строки, реализующие для каждого типа в башне проецирование на предыдущий:

;integer package
;rational package
(define (rational->integer r)
  (make-integer (round (/ (numer r) (denom r)))))
(put 'project '(rational) rational->integer)
;real package
(define (real->rational x)
  (make-rational (numerator x) (denominator x)))
(put 'project '(real) real->rational)
; complex package
(define (complex->real z)
  (make-real (real-part z)))
(put 'project '(complex) complex->real)

Преобразование комплексного числа к действительному и рационального к целому особых трудностей не вызывает, а вот получить из действительного числа рациональное уже сложнее. Первоначально я хотел выбросить рациональные числа из башни и сделать упражение без них, но потом подглядел у Эли Бендерски, что в Scheme есть встроенные процедуры numerator и denominator, которые получают числитель и знаменатель по действительному числу. Это как раз то, что мне было нужно для получения рационального числа из действительного.

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

(define (drop x)
  (let ((op (get 'project (type-tag x))))
    (if op
        (let ((p (project x)))
          (if (equ? x (raise p))
              (drop p)
              x))
        x)))

Для того, чтобы внедрить упрощение результатов операций с помощью drop в apply-generic достаточно просто обернуть в drop результат вызова apply. Соответствующая строчка будет иметь вид:

(drop (apply proc (map contents args)))

Больше никаких изменений apply-generic не требует.

Comments

Comment from nobody
Date: July 16, 2008, 12:05 pm

Если бы round возвращал целое число, а не дробное, то можно было бы сделать преобразование действительного в рациональное так:

(define epsilon 0.0001)
(define coeff (round (/ 1 epsilon)))
(define (real->rational x)
(make-rational (round (* x coeff))
coeff))

Comment from thror
Date: May 27, 2009, 11:49 pm

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

Comment from thror
Date: June 10, 2009, 7:01 am

и тут оказывается, что ничего работать не будет. Объясню. Допустим, как у вас, функцию raise мы определили через apply-generic. Смотрите. Например raise от (scheme-integer . 5) сначала сделает (rational . (5 . 1)) а потом попытается опустить… Смотрите теперь на свою функцию drop внимательнее… В ней вызовется project который сделает снова (scheme-integer . 5) и внимание… Вызовется снова raise от (scheme-integer . 5) результат такой - вызов raise почти с любым аргументом Х приведет к рекурсивному вызову raise от того же Х…

Comment from thror
Date: June 10, 2009, 7:05 am

причина этого в том, что raise и project лишь инструменты для правильного функционирования apply-generic, и не должны быть реализованы через нее.

Write a comment