globalf5-240972240972 (850810), страница 26
Текст из файла (страница 26)
Вместо создания отдельной программы, оперирующеймассой данных, приходится разбираться с данными, которые самиобладают поведением, а программа сводится к простомувзаимодействию новой категории данных — " объекты ".Чтобы сравнить дистанцию с функциональным программированием,рассмотримсамодельныйвстроенныйвЛиспобъектноориентированный язык (ОО-язык), обеспечивающий основы ООП.Встраивание ОО-языка — идеальный пример, показывающийхарактерное применение функционального программирования, прикотором типичные понятия ООП отображаются в фундаментальныеабстракции.При организации наследования следует пояснить разницу междумоделями обобщенных функций и обмена сообщениями.объекты обладают свойствами,173Л.В.
ГородняяОсновы функционального программированияпосылают сообщения,наследуют свойства и методы от предков.Рассмотрим программу вычисления площадей ряда геометрическихфигур, таких как круги, прямоугольники, звезды и пр. Ее запись в ООстиле поначалу может выглядеть непринципиально отличающейся отобычной программы.Но допустим, понадобилось ввести новый класс — раскрашенныекруги. Если методы раскраски произвольных фигур уже существовали,то можно унаследовать две особенности:1.
от круга наследуем понятие "радиус", а от раскрашенных — "цвет";2. можно не вводить метод "раскраска круга".Практически ООП — это организация программ в терминах методов,классов, экземпляров и наследования. Почему стоит писать программытаким способом? Основной выигрыш — программы легче изменять.Если мы хотим изменить способ манипулирования каким-либообъектом некоторого класса, то мы изменяем лишь метод этого класса.Если мы хотим сделать нечто подобное объекту, но отличающееся вотдельных чертах, мы можем создать подкласс объектов, а уже дляподкласса поменять его отдельные черты. Если программа написанатщательно, то можно добиться того, чтобы все такие модификациипроизводились даже без просмотра ранее написанного исходного текстапрограммы.В Лисп есть разные способы размещать коллекции свойств.
Один изних — представлять объекты как хэш-таблицы и размещать свойства каквходы в нее. В [7] приведен блестящий пример реализации ООП на базехэш-таблиц. Тогда отдельное свойство можно получать из таблицы вформе:(gethash 'color obj)Поскольку функции являются данными объекта, мы можем размещать ихточно так же, как и свойства. Это значит, что мы должны завести ещеметоды, позволяющие вызывать данный метод объекта как исполнениесвойства данного объекта.174Л.В. ГородняяОсновы функционального программирования(FUNCALLl (gethash 'move obj) obj 10)Мы можем определить под эту идею синтаксис как в языке Smalltalk(DEFUN tell (obj message &rest args)(APPLY (gethash messmage obj) obj args))что позволяет сказать объекту, чтобы он переместился на 10 шагов, вформе:(tell obj 'move 10)Фактическиуспех наследования обеспечиваетединственнаяособенность Лиспа: все это работает благодаря реализации рекурсивнойверсии GETHASH.
Таким образом, определение данной функции намсразу даст все три основные черты ООП.Посмотрим, во что это обойдется в исходном примере.Мы должны создать два объекта, один — потомок другого.В объект "круги" мы помещаем методы для всех кругов. Для начала этофункция одного аргумента — объекта, посылающего сообщение:(SETF (gethash 'area circle-class)#' (LAMBDA (x)(* pi (expt (rget ' radius x) 2))))Теперь можно спрашивать о площади круга, она будет вычислятьсясогласно методу, определенному для класса.
Мы используем rget причтении свойства и tell — при вызове метода.(rget 'radius our-cicle)(tell our-circle 'area)Прежде чем улучшать эту программу, надо проверить, что жеполучилось. Легкость результата — это трюк, но не программистскийтрюк, а концептуальный.
Не будем забывать о том, что Лисп по своейприроде уже был ОО-языком, или даже чем-то более общимизначально. Все что нам понадобилось — это создать новый фасад дляуже существующих в Лиспе абстракций.175Л.В. ГородняяОсновы функционального программированияМножественное наследованиеДо сих пор речь шла о простом наследовании — объект имел толькоодного предка. Но можно получать и множественное наследованиепостроением списка свойств предков со слегка измененной rget. Припростом наследовании, когда нам надо выбрать некоторое свойствообъекта, мы сразу рекурсивно ищем его предшественников. Еслисобственно объект не содержит искомую информацию, мы обозреваемего предка и т.д.
При множественном наследовании мы делаемпримерно то же самое, но работа усложняется тем, чтопредшественники объекта могут образовывать граф вместо дерева.При реализации такой идеи не следует проверять объект ранее егопоследователей ( a b c d ):(d)/\(b) (c)\/(a)Если это сделать прямым сцеплением списков, то результат слишкомнеэффективен.Определяемые объектыПервое улучшение — функция создания объектов. Такая функция можетбыть не видна пользователю.
Если мы создадим данную функцию,можно будет строить объекты и объявлять его предков за один шаг. Мыполучим список предшествования объектов в процессе их созданиявместо дорогостоящей их реорганизации при поиске свойства.Стратегия — поддерживать список всех существующих объектов, и внем помечать списки изменяемых предшественников. Это тожеобременительно, но без потери гибкости основная нагрузка переноситсяв нечасто повторяющуюся область.176Л.В. ГородняяОсновы функционального программированияФункциональный синтаксисДругое улучшение — синтаксис сообщений можно варьировать безоглядки на традицию префиксных записей.
Определение методов можетдостичь предельной гибкости благодаря возможности генерироватьопределения функциональных объектов с помощью DEFMACRO.ЭкземплярыДо сих пор не требовалось особо различать классы, объекты и ихвхождения. Логически удобно все обрабатывать по общей схеме, новесьма непроизводительно всякий раз просматривать сотнипредшественников, если известно, что достаточно менее десятка. Такуюоптимизацию выполняет механизм экземпляров. Экземпляр имеетодного предка — его класс. Его обработка не требует спискапредшественников.
Это не ведет к потере гибкости, т.к. принеобходимости можно переопределить состояние объекта и как быконвертировать экземпляр в класс.Векторная реализацияРеализация ООП с помощью хэш-таблиц обладает слегкапарадоксальной окраской: гибкость у нее больше, чем надо, и забольшую цену, чем можно позволить. Уравновесить это может подобнаяреализация на базе простых векторов. Этот переход показывает, какфункциональное программирование дает новое качество "на лету". Вопорной реализации фактически не было реализационного разделенияобъектов на экземпляры и классы. Экземпляр — это был просто класс содним-единственным предком. При переходе к векторной реализацииразделение на классы и экземпляры становится реальным.
В нейстановится невозможным превращать экземпляры в классы простымизменением свойства.Еще одна реализация177Л.В. ГородняяОсновы функционального программированияБолее прозрачная модель ООП получается на базе обычных списковсвойств, заодно иллюстрирующая глубинное родство ФП и ООП:DEFUN classes (cl) (COND(cl (CONS (cdar cl) (classes (CDR cl)))) )); вывод формулы классов аргументов из определения; параметров метода; NIL — произвольный класс(DEFUN argum (cl) (COND(cl (CONS (caar cl) (argum (CDR cl)))) )); вывод списка имен аргументов из определения; параметров метода(defun defmet (FMN c-as expr)(setf (get FMN 'category) 'METHOD)(setq ML (cons(cons(cons FMN (classes c-as))(list 'lambda (argum c-as) expr) ) ML))FMN ); объявление метода и расслоение его определения; для удобства сопоставления с классами аргументов(DEFUN defcl (NCL SCL FCL ); имя, суперкласс и поля/слоты класса(SETQ ALLCL (CONS NCL ALLCL))(SET NCL (append FCL SCL)) ); значением класса является список его полей,; возможно, со значениями(DEFUN ev-cl (vargs) (COND; вывод формата фактических аргументов для поиска; метода их обработки(vargs (CONS (COND178Л.В.
ГородняяОсновы функционального программирования((member (caar vargs) ALLCL)(caar vargs)) )(ev-cl (CDR vargs)))) )); NIL если не класс(DEFUN m-assoc (pm meli) (COND(meli (COND ((equal (caar meli) pm)(cdar meli))(T (m-assoc pm (CDR meli))))))); поиск подходящего метода, соответствующего; формату классов данных(DEFUN method (MN args &optional c)(APPLY (m-assoc (CONS mn (ev-cl args)) ML)args c)); если метода не нашлось, в программе следует; выполнить приведение; параметров к нужному классу(DEFUN instance (class &optional cp) (COND; подобно Let безымянная копия контекста(DEFUN instance (class &optional cp) (COND; подобно Let безымянная копия контекста(class (COND ((ATOM (CAR class))(instance (CDR class) cp))((assoc (caar class) cp)(instance (CDR class) cp))(T(instance (CDR class)(CONS (CAR class)cp))))) ) cp)(DEFUN slot (obj fld) (assoc fld obj)); значение поля объекта179Л.В.
ГородняяОсновы функционального программированияОстается лишь слегка подкорректировать определение Лиспинтерпретатора, заодно используя необязательные параметры,освобождающие от простейших вспомогательных определений,напримеротобязательноговхождениянакопителейтипаассоциативного списка.(DEFUN evcon- (c &optional a);|_________ключ, объявляющий;необязательные параметры(COND((eval-p (CAR (CAR c)) a)(eval-p (CAR (CDR (CAR c))) a) )(T (evcon- (CDR c) a) ) ))(DEFUN evlis- (m &optional a)(COND((EQ m NIL) NIL)( T (CONS (eval-p (CAR m) a)(evlis- (CDR m) a) ) ) ))(DEFUN eval-p (e &optional c)(COND ((ATOM e) (value e c))((ATOM (CAR e))(COND((EQ (CAR e) 'QUOTE) (CAR (CDR e)))((EQ (CAR e) 'COND) (evcon- (CDR e) a))((get (CAR e) 'METHOD)(method (CAR e) (evils (CDR e)) c) )(T (apply-p (CAR e)(evlis- (CDR e) c) c))))(T (apply-p (CAR e)(evlis- (CDR e) c) c))))(DEFUN apply-p (f args &optional c)(COND ((ATOM f) (apply-p (function f c) args c))((ATOM (CAR f))(COND ((get (CAR f) 'macro)(apply-p (apply-p (get (CAR f) 'macro)180Л.В.
ГородняяОсновы функционального программирования(CDR f) c)args c))(T(apply-p (eval f c) args c))))(T (apply-p (eval f c) args c))))(print (eval-p 1))(print (eval-p 'a))(print (eval-p '(QUOTE b)))(print (eval-p '(COND (NIL 6)(T 88) )))(print (eval-p '(CAR '(3 2))))Средства ООП в CLOS на базе стандарта ClispПоказанный в [7] пример работает по первому аргументу (выборподходящего метода рассчитан на то, что достаточно разобраться содним аргументом), CLOS делает это на всех аргументах, причем срядом вспомогательных средств, обеспечивающих гибкий переборметодов и анализ классов объектов.Классы и экземпляры объектов(defclass ob () (f1 f2 ...))Это означает, что каждое вхождение объекта будет иметь поля-слоты f1f2 ...














