Общие Функции Clos

Любой разработчик, так или иначе сталкивавшийся с объектно-ориентированным программированием и пытавшийся в нем разобраться, обязательно слышал об ЗАКРЫТЬ , объектная система языка Common Lisp, одной из фундаментальных особенностей которой являются так называемые «обобщенные функции», или, в просторечии, «мультиметоды».

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

Было бы даже не совсем корректно говорить, что это расширение диспетчеризации self/this, то есть «виртуальных функций», на несколько аргументов.

Конечно, множественная диспетчеризация — одна из основных особенностей обобщенных функций, но сама их суть не только и даже не столько в этом.

Поясню на примере – калькуляторе простых математических выражений.

Если мы возьмем язык с «классической» реализацией ООП, например C#, то в итоге получим что-то вроде этого:

  
  
   

interface IExpression { int Evaluate(); } class Negation : IExpression { IExpression expr; public Negation(IExpression e) { expr = e; } public int Evaluate() { return -expr.Evaluate(); } } class Addition : IExpression { IExpression left, right; public Addition(IExpression l, IExpression r) { left = l; right = r; } public int Evaluate() { return left.Evaluate() + right.Evaluate(); } } class Number : IExpression { int value; public Number(int v) { value = v; } public int Evaluate() { return value; } }

Версия Lisp будет такой:

(defstruct (negation (:constructor negation (expr)) (:conc-name nil)) expr) (defstruct (addition (:constructor addition (left right)) (:conc-name nil)) left right) (defgeneric evaluate (expr) (:method ((expr negation)) (- (evaluate (expr expr)))) (:method ((expr addition)) (+ (evaluate (left expr)) (evaluate (right expr)))) (:method ((expr number)) expr)) ;; (evaluate (addition 5 (negation -5))) ;; ==> 10

Первое, что бросается в глаза, помимо уменьшения размера кода, — это то, что методы отделены от структур данных и сгруппированы в общие функции.

Второе заметное отличие — отсутствие интерфейсов как таковых.

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

оценивать по стандартному классу число .

Вообще отсутствие в CLOS понятий интерфейсов, в ОО-понимании этого слова, и абстрактных классов, довольно сильно, по моим наблюдениям, мешает портированию кода с традиционных объектно-ориентированных языков, а кроме того , создает дополнительные препятствия в изучении языка и объектной модели для людей, привыкших к этим самым традиционным объектно-ориентированным языкам; Кроме того, «требование единообразия сигнатуры универсальной функции» (что означает, что все методы в группе должны иметь одинаковое количество обязательных параметров) создает проблемы для таких людей, поскольку эти люди начинают писать в традиционном объектно-ориентированном стиле, просто предоставляя предполагаемое «это» в качестве первого аргумента.

Возможно, некоторым из них также неудобно отсутствие модификаторов доступа, «свойств» в классах и разделение наследования интерфейса и реализации.

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

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

CLOS смещает фокус с состояние И поведение субъекты своим взаимодействие друг с другом и с внешним миром.

Я выше писал, что в CLOS нет интерфейсов.

На самом деле это не совсем так — универсальные функции и есть интерфейсы, а их методы являются реализациями.

В этот момент кто-то из приверженцев функционального программирования может спросить: «Отлично, а чем это отличается от банального сопоставления с образцомЭ» Он отличается во многих отношениях.

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

Во-вторых, методы можно добавлять к обобщенным функциям, причем даже прямо во время выполнения.

И в-третьих, диспетчеризацией можно управлять как угодно, назначая родовым функциям так называемые «комбинаторы методов», и это не говоря уже о декораторах :before, :after и :around для стандартного комбинатора.

О комбинаторах методов Я когда-то писал в своем ЖЖ , желающие могут прочитать, а здесь я покажу, как, используя один из встроенных комбинаторов, программа , реализуйте что-то похожее на «конструкторы» из обычных объектно-ориентированных языков:

(defgeneric construct (object &key) (:method-combination progn :most-specific-last)) (defun new (class &rest args &key &allow-other-keys) (let ((object (make-instance class))) (apply #'construct object args) object)) (defclass superclass () (some-slot)) (defclass subclass (superclass) ()) (defmethod construct progn ((object superclass) &key value) (setf (slot-value object 'some-slot) value) (write-line "Superclass constructed")) (defmethod construct progn ((object subclass) &key) (write-line "Subclass constructed") (format t "Slot value: ~s~%" (slot-value object 'some-slot))) ;; (new 'subclass :value 123) ;; ==> Superclass constructed ;; Subclass constructed ;; Slot value: 123

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

Обидно, что мейнстрим принимает ее идеи крайне медленно и неохотно, а воплощает их в жизнь еще хуже.

Теги: #Lisp #common lisp #clos #множественная диспетчеризация #мультиметоды #универсальные функции #множественная диспетчеризация #мультиметоды #универсальные функции #ООП #Lisp

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.