Стили и методы программирования

         

Объекты и LISP


Стандартная надстройка над Common Lisp, имитирующая объектно-ориентированный стиль, это модуль CLOS — Common Lisp Object System. Сама по себе объектность не дает никакого выигрыша по сравнению с языком LISP, поскольку возможности динамического вычисления функций в LISP даже шире. Видимо, именно поэтому в CLOS имеются две интересных модификации, делающие его не совсем похожим на стандартное ООП.

Начнем с понятия структуры данных в языке Common Lisp. Структура определяется функцией defstruct вида

(defstruct pet name (species ’cat) age weight sex)

Задание структуры автоматически задает функцию-конструктор структуры make-pet, которая может принимать ключевые аргументы для каждого из полей:

(make-pet :nick ’Viola :age ’(3 years) :sex ’femina))

и функцию доступа для каждого из полей, например pet-nick, использующуюся для получения значения поля или ссылки на него. Если поле не инициализировано (ни по умолчанию, ни конструктором), оно получает начальное значение NIL. Никакой дальнейшей спецификации полей структур нет6).

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

(defclass pet (animal possession) ( (species :initform ’cat) (nick :accessor nickof :inintform ’Pussy :initarg namepet) )

Этот класс наследует поля, функции доступа и прочее от классов animal и possession. Например, поле cost имеется в значении класса, если оно имеется в одном из этих классов. Поскольку статических типов у полей нет, нет и конфликтов.

Основная функция наследования в CLOS — определение упорядочения на классах. С каждым классом связано свое упорядочение. Наследник меньше своих предков, из предков меньшим считается тот, который раньше перечислен в списке наследования при определении класса. CLOS достраивает этот частичный порядок до линейного. Способ пополнения порядка может быть в любой момент и без оповещения изменен, и хакерское использование особенностей конкретного пополнения считается грубой стилистической ошибкой.
Если система находит несовместимость в определении порядка, то она выдает ошибку, как в следующем примере:

[6]> (defclass init () ()) #<STANDARD-CLASS INIT> [7]> (defclass a (init) ()) #<STANDARD-CLASS A> [8]> (defclass b (init) ()) #<STANDARD-CLASS B> [9]> (defclass c1 (a b) ()) #<STANDARD-CLASS C1> [10]> (defclass c2 (b a) ()) #<STANDARD-CLASS C2> [11]> (defclass contr (c1 c2) ()) *** - DEFCLASS CONTR: inconsistent precedence graph, cycle (#<STANDARD-CLASS A> #<STANDARD-CLASS B>)

В CLOS могут задаваться методы, отличающиеся от функций тем, что их аргументы специфицированы, например

(defmethod inspectpet ((x pet) (y float)) (setf weightofanimal 3.5))

Как видно из этого примера, методы не обязательно связаны с классами. Они могут быть связаны с любыми типами. Методы в CLOS могут иметь дополнительные спецификации. Для того, чтобы разобраться, как эти спецификации взаимодействуют с упорядочением типов классов, рассмотрим следующую программу и генерируемый при ее исполнении результат.

(defclass thing () ((weight :initform ’(0 kg) :accessor weightof :initarg :weight))) (defclass animal (thing) ((specie :accessor specieof :initarg :spec) (sex :accessor sexof :initform ’m :initarg :sex))) (defclass possession (thing) ((owner :accessor ownerof :initform ’nnn) (cost :accessor costof :initform ’(0 bucks) :initarg :cost)) ) (defclass person (animal) ((specie :initform ’human) (name :initarg :thename :accessor nameof))) (defclass pet (animal possession) ((nick :initarg :thenick :accessor nickof) (specie :initform ’cat)))

(defmethod act :before ((p pet)) (print "Cat mews")) (defmethod act :after ((p pet)) (print "Cat turns")) (defmethod act :around ((p pet)) (progn (print "You have a cat") (call-next-method)))

(defmethod act ((p animal)) (progn (print "Animal is close to you") (call-next-method))) (defmethod act :before ((p animal)) (print "You see an animal")) (defmethod act :after ((p animal)) (print "You send the animal off")) (defmethod act :around ((p animal)) (progn (print "You don’t like wild animals") (call-next-method)))



(defmethod act ((p possession)) (progn (print "You test your property") (call-next-method))) (defmethod act :before ((p possession)) (print "You see your property")) (defmethod act :after ((p possession)) (print " You are pleased by your property")) (defmethod act :around ((p possession)) (progn (print "You admire your property if it is in good state") (call-next-method)))

(defmethod act ((p thing)) (print "You take the thing")) (defmethod act :before ((p thing)) (print "You see something")) (defmethod act :after ((p thing)) (print "You identified this thing")) (defmethod act :around ((p thing)) (progn (print "You are not interested in strange things") (call-next-method)))

(act (make-instance ’pet :thenick "Viola" :cost ’(25 kop)))



Листинг 8.6.1. Взаимодействие дополнительных спецификаций методов в CLOS с упорядочением типов классов

При загрузке этого файла происходит следующее:

[1]> (load ’myclasses) ;; Loading file E:\clisp-2000-03-06\myclasses.lsp ... "You have a cat" "You don’t like wild animals" "You admire your property if it is in good state" "You are not interested in strange things" "Cat mews" "You see an animal" "You see your property" "You see something" "Cat purrs" "Animal is close to you" "You test your property" "You take the thing" "You identified this thing" "You are pleased by your property" "You send the animal off" "Cat turns" ;; Loading of file E:\clisp-2000-03-06\myclasses.lsp is finished. T

Пример 8.6.2. Результат загрузки программы 8.6.1

Видно, что упорядоченность классов по отношению наследования позволяет выстраивать целые последовательности действий при вызове одного метода.

Поскольку в CLOS нет ни механизмов скрытия конкретных представлений, ни механизмов замены прямого доступа к данным на функции, ни других характерных особенностей ООП, мы видим еще один пример того, как модным словом (в данном случае ООП) прикрывается другая, не менее интересная сущность: начатки планирования действий по структуре типов данных.


В связи с этим стоит напомнить блестящий эксперимент планирования вычислений по структуре данных, в настоящий момент (судя по всему, временно) забытый: эстонскую систему PRIZ [28].

Неадекватное теоретизирование мешает увидеть и развить реальные достоинства системы и закрепляет слабые места.

  1)

  И уж точно первый из выживших, поскольку Plankalkul Цузе умер вместе с его машинами.

  2)

  А для предотвращения хакерских трюков и нежелательно!

  3)

  Любезно предоставленный Л. Городней (ИСИСО РАН и НГУ)

  4)

  Как сделано в системе TEX-LATEX.

  5)

  Примером, иллюстрирующим ситуацию, может служить XML, линейность конкретного синтаксиса которого является лишь следствием стихийных стандартов.

  6)

  Имеется редко используемая возможность указать, какого типа должно быть поле.

Содержание раздела