2.1. Введение в абстракцию данных
101
Эта идея ярко иллюстрируется тем, что мы могли бы реализовать cons, car и cdr без
использования каких-либо структур данных, а только при помощи одних процедур. Вот
эти определения:
(define (cons x y)
(define (dispatch m)
(cond ((= m 0) x)
((= m 1) y)
(else (error "Аргумент не 0 или 1 -- CONS" m))))
dispatch)
(define (car z) (z 0))
(define (cdr z) (z 1))
Такое использование процедур совершенно не соответствует нашему интуитивному по-
нятию о том, как должны выглядеть данные. Однако для того, чтобы показать, что это
закон ный способ представления пар, требуется только проверить, что эти процедуры
удовлетворяют вышеуказанному условию.
Тонкость здесь состоит в том, чтобы заметить, что значение, возвращаемое cons,
есть процедура, — а именно процедура dispatch, определенная внутри cons, которая
принимает один аргумент и возвращает либо x, либо y в зависимости от того, равен ли
ее аргумент 0 или 1. Соответственно, (car z) определяется как применение z к 0. Сле-
довательно, если z есть процедура, полученная из (cons x y), то z, примененная к 0,
вернет x. Таким образом , мы показали, что (car (cons x y)) возвращает x, как нам
и хо телось. Подобным образом (cdr (cons x y)) при меняет процедуру, возвращае-
мую (cons x y), к 1, что дает нам y. С ледовательно, эта процедурная реализация пар
закон на, и если мы обращаемся к парам только с помощью cons, car и cdr, то мы не
сможем отличить эту ре ализацию от такой, которая использует «настоящие» структуры
дан ных.
Демонстрировать процедурную реализацию имеет смысл не для того, чтобы пока-
зать, как работает наш язык (Scheme, и вообще Лисп-системы, реал изуют пары напря-
мую из соображений эффективности), а в том, чтобы показать, что он мог бы работать
и так. Пр оцедурная реализация, хотя она и выглядит трюком, — совершенно адекват-
ный способ представления пар, поскольку она удовлетворяет единственному условию,
которому должны соответствовать пары. Кроме того, этот пример показывает, что спо-
собность работать с процедурами как с объектами автоматически дает нам возможность
представлять составные данные. Сейчас это может показаться курьезом, но в нашем про-
граммистском репертуаре процедурные представления данных будут играть центральную
роль. Такой стиль программирования часто называют передачей сообщений (message
passing), и в главе 3, при рассмотрении вопросов моделирования, он будет нашим основ-
ным инструментом.
Упражнение 2.4.
Вот еще одно процедурное представление для пар. Проверьте для этого представления, что при
любых двух объектах x и y (car (cons x y)) возвращает x.
(define (cons x y)
(lambda (m) (m x y)))