LINUX.ORG.RU

Два подхода к контекстам

 , ,


3

2

В этом посте я собираюсь рассмотреть различия в объектной модели Smalltalk и CLOS и как эти модели связаны с понятием контекста. Поклонники статической типизации могут не читать. С открытием CLOS возникли споры о том, CLOS — это что? ООП или не ООП? Становление новой науки неизбежно приводит к терминологическим спорам. ООП — термин расплывчатый и ИМХО, его следовало бы избегать. Как CLOS, так и Smalltalk реализуют одну важную фичу — ad hoc полиморфизм. Эта фича крайне важна для программирования, т.к. позволяет настраивать уже существующий код без изменения его исходного текста. Модель Smalltalk имеет ограниченный ad hoc полиморфизм, т.к. фактически позволяет производить диспетчеризацию лишь по одному аргументу. Однако, кроме ad hoc полиморфизма есть еще одна вещь, связанная с ООП — инкапсуляция. Итак, кратко опишем две ОО модели:

  • Инкапсуляция и ad-hoc полиморфизм (Smalltalk).
  • Ad-hoc полиморфизм без инкапсуляции (CLOS).

Далее я покажу, что эти два подхода противостоят друг другу. В Smalltalk объект — самодостаточная сущность. Чтобы распечатать объект (получить его внешнюю репрезентацию) необходимо послать объекту соответствующее сообщение. Это означает, что внешняя репрезентация объекта зависит только от него, и в минимальной степени зависит от внешнего контекста вызова. В CLOS внешняя репрезентация объекта целиком и полностью зависит от текущей обобщенной функции print-object. Теоретически у одного экземпляра Lisp системы может быть много различных обобщенных функций print-object.

Обычно естественные языки имеют лишь одно текстовое представление. Это не так для иероглифических языков, и скорее всего мы придём со временем к ситуации, когда один и тот же язык будет иметь множество проекций на текст. К этому же идет и эволюция языков программирования. Так, в Perl 6 функция load принимает на вход грамматику, которая описывает Perl 6 и написана на Perl 6. Далее свойство независимости от внешнего представления мы будем называть синтаксической абстракцией. ЯП, наиболее полно поддерживающий синтаксическую абстракцию — Lisp. Программы на Lisp записываются в виде S-выражений, но скобки тут нужны только для того, чтобы указать ридеру структуру. В Lisp текстовая репрезентация программы называются выражениями, а вычислитель работает не с выражениями, а с формами. Термин форма подчеркивает абстрактную природу вычислителя. Я ранее уже писал о том, как можно усилить синтаксическую абстракцию символов в Lisp.

Итак, синтаксическая абстракция предполагает, что объект, помещенный в различные контексты может иметь различные внешние представления. Модель Smalltalk не поддерживает синтаксическую абстракцию, т.к. объект сам должен вернуть свою репрезентацию в ответ на соответствующее сообщение. Давайте теперь обобщим эти рассуждения оторвав их от печати и считывания, на примере которых понятно что, инкапсуляция означает контекстную изоляцию.

Контекст — крайне важное понятие. Игнорирование контекста и абсолютизация понятий приводит к проблемам. Как определить тип объекта? Широко известен спор между Платоном и Диогеном. «Человек, - сказал Платон, - это двуногое животное без перьев». Тогда Диоген ощипал петуха и со словами: «Вот твой человек». Платону пришлось сделать уточнение: «Двуногое животное без перьев и имеющее ногти». Понятно, что тип объекта зависит от наблюдателя. Языки программирования начали с простой идеи — к каждому объекту прилеплен бейджик с его типом. В Smalltalk человеком является тот, кто на вопрос «ты кто?» отвечает — человек. Какой может быть типизация, которая учитывает контекст? Она носит название предикатная типизация. Еще иногда эту идею называют утиной типизацией. В этом подходе тип объекта зависит от того, кто и какие вопросы задает объекту. Платон может определить человека по отсутствию перьев и наличию ногтей, а Диогену нужен фонарь, чтобы определить, кто есть человек.

Одна из наиболее важных идей в истории программирования — гомоиконность является примером переключения контекста. В Lisp нет специально выделенного типа для AST. Является ли определенное дерево AST или нет, зависит от намерений исполнителя. Благодаря этому стало возможным метапрограммирование без значительного усложнения языка. Язык, который выделяет отдельный тип для AST должен иметь весь набор селекторов и конструкторов для этого типа, тогда как представление AST формами дает возможность пользоваться общими селекторами и конструкторами.

Я не отвергаю полностью концепцию инкапсуляции, но хочу обратить внимание, что ее абсолютизация (как в Smalltalk) противоречит некоторым принципам заложенным в динамические языки.

ООП — термин расплывчатый и ИМХО, его следовало бы избегать.

Вот в этом ты прав.

Debasher ★★★★★ ()

а что означает диспетчеризация по одному аргументу? На пальцах можете объяснить, не влазя в дебри?

J-yes-sir ()
Ответ на: комментарий от J-yes-sir
объект.имя_метода(аргумент_1, ..., аргумент_n)

Какой именно метод вызывается зависит от того, какому классу принадлежит объект и не зависит от аргументов переданных методу. Это и есть самое важное в ООП и называется ad hoc полиморфизмом. В CLOS выбор метода зависит от всех аргументов. Т.е. в CLOS код выше будет записан так:

имя_метода(объект, аргумент_1, ..., аргумент_n)

Таким образом ООП на сообщениях — частный случай CLOS, если учитывать только свойство ad hoc полиморфизма.

komputikisto ()

объект сам должен вернуть свою репрезентацию в ответ на соответствующее сообщение.

А в википедии написано

является ли операция правильной, определяет объект-получатель, а не компилятор

неувязочка-с.

J-yes-sir ()
Ответ на: комментарий от J-yes-sir

неувязочка-с.

Что-то я не вижу противоречия. Все зависит от объекта — и тип и текстовая репрезентация и является ли сообщение правильным (может ли его обработать объект).

komputikisto ()
Ответ на: комментарий от komputikisto

Да не, это, по моему, такая же модель, как в тикле, там вообще нет типов, там тоже корректность типа аргумента определяется непосредственно функцией, которой этот аргумент передается. По-анологии — ф-ция - объект, которому отсылается сообщение — аргумент. То есть, смоллток, получается, вообще безтиповый язык. Это Ъ, по моему.

J-yes-sir ()
Ответ на: комментарий от J-yes-sir

Ты описываешь — это утиная типизация. Реальность сложнее, чем твоя аналогия. Тут еще примешивается ad hoc полиморфизм, которого в тикле нет без определенных расширений. Посмотри CLOS — все по поводу типов и ООП встанет на свои места. Обобщенные функции CLOS есть еще в Perl 6, но они там перемешаны с традиционным ООП. Интересно, а аналог CLOS для тикля есть?

komputikisto ()

Не угадал автора. Думал, что terminator-101. :)

Zubok ★★★★★ ()

Далее я покажу, что эти два подхода противостоят друг другу. В Smalltalk объект — самодостаточная сущность. Чтобы распечатать объект (получить его внешнюю репрезентацию) необходимо послать объекту соответствующее сообщение. Это означает, что внешняя репрезентация объекта зависит только от него, и в минимальной степени зависит от внешнего контекста вызова

И вот тут ты не прав. Внешний контекст вызова — это вообще непонятно о чем. Объект — это и есть контекст, по сути. Объект получает сообщение, и интерпретирует его в своем контексте. Именно такие языки и являются максимально контекстозависимыми. А в лиспе, по факту, кроме лексического контекста и нет особо ничего, по дефолту. Как раз в таких вот динамичных языках как смолток, javascript и прочие, существует интерпретация во множестве контекстов, в лиспе же — только лексический.

J-yes-sir ()
Ответ на: комментарий от Zubok

Его забанили же. Я вот теперь не могу понять кто новая инкарнация: ТС или J-yes-sir.

mix_mix ★★★★★ ()
Ответ на: комментарий от komputikisto

Интересно, а аналог CLOS для тикля есть?

Вряд ли. Я слышал, что тиклеры смотрят на лисп как на переусложненное неповоротливое говно. А на клоз — тем более. Не думаю, что кто-то в здравом уме захотел бы это сделать. Хотя не знаю, утверждать не буду.

J-yes-sir ()
Ответ на: комментарий от komputikisto

объект.имя_метода(аргумент_1, ..., аргумент_n)
имя_метода(объект, аргумент_1, ..., аргумент_n)

А это вообще одно и то же, различия только в синтаксисе. Второе — просто более неуклюжий вариант записи первого. В JS катят оба варианта, думаю, в смоллтоке также. Первый вариант гораздо удобней, на самом деле, потому что позволяет строить такие цепочки

person("Jack").age().oldEnoughToWatchPornFilm(showPorn("Big Dicks in New Jersey"))
А во втором варианте задолбаешься скобки писать, вот и вся разница. А остальное все — это демагогия.

J-yes-sir ()

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

ilammy ★★★ ()
Ответ на: комментарий от mix_mix

Я вот теперь не могу понять кто новая инкарнация: ТС или J-yes-sir.

Определенно второй.

tailgunner ★★★★★ ()
Ответ на: комментарий от J-yes-sir

То есть, просто явно проталкиваешь контекст, а там он динамически связывается. Все правильно.

anonymous ()
Ответ на: комментарий от ilammy

Достаточно просто перестать считать, что именно объекты — это пуп земли

Вот как раз основа идеологии смолтоко-подобных языков — это абстрагирование от объекта, и концентрация на сообщениях.

«Я жалею, что придумал термин «объекты» много лет назад, потому что он заставляет людей концентрироваться на мелких идеях. По-настоящему большая идея — это сообщения».(Ц сам знаешь кто)

А вот клоз, как раз, вообще непонятно о чем. Наверное, они просто решили собрать все базворды в одну кучу, и «реализовать» их в суперсложной системе, просто чтоб було.

J-yes-sir ()
Ответ на: комментарий от ilammy

Я сейчас размышляю над предикатной диспетчеризацией. Проблема состоит в том, что предикаты не образуют иерархии, которая нужна чтобы отсортировать список применимых методов (который используется в функции call-next-method). Я предполагаю такое решение: каждый предикат имеет ссылку на предикат определяющий супертип. Наверху этой иерархии будет предикат возвращающий истину для любого объекта. Т.е. предикат — структура хранящая функцию и ссылку на другой предикат. Может быть я не первый об этом думаю? Ты слышал о таком решении?

komputikisto ()
Ответ на: комментарий от J-yes-sir

А в лиспе, по факту, кроме лексического контекста и нет особо ничего, по дефолту.

В посте термин «контекст» понимается обобщенно.

komputikisto ()
Ответ на: комментарий от komputikisto

Я не думаю, что имеет практический смысл определять подтипизацию для предикатов в контексте объектной системы. Это ж алгоритмически неразрешимая задача — определить в общем случае, является ли один предикат подтипом другого.

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

А иерархическую организацию подтипов объектов прекрасно описывают классы.

ilammy ★★★ ()

Как CLOS, так и Smalltalk реализуют одну важную фичу — ad hoc полиморфизм. Эта фича крайне важна для программирования, т.к. позволяет настраивать уже существующий код без изменения его исходного текста.

Википедия по поводу полиморфизма говорит:

Два наиболее различных из них были описаны Кристофером Стрэчи[en] в 1967 году: это специальный полиморфизм (или «ad hoc полиморфизм») и параметрический полиморфизм. Кратко специальный полиморфизм описывается принципом «много реализаций с похожими интерфейсами», а параметрический полиморфизм — «одна реализация с обобщённым интерфейсом».

Объясните, если не сложно, почему Smalltalk - ad hoc полиморфизм? Разве в нем на каждый набор параметров реализуется по функции с одинаковыми именами?
И каким образом полиморфизм помогает настраивать существующий код без его изменения?

TDrive ★★★★★ ()
Ответ на: комментарий от ilammy

Ты не понимаешь, что такое объект в истино-объектноориентированных языках. Объект — это абстрактная сущность, она может быть даже не прописана и не определена явно. Это есть настоящее метапрограммирование, не то что в этих ваших лиспах, где все сводится к костылянию синтаксиса, я вообще не понимаю, почему вы называете это метапрограммированием, lol. Если я написал конпелятор какого-нибудь недоязычка, я, значит стал «метапрограммистом» с точки зрения лиспфанбоя, да?

anonymous ()

И почему важен именно ad hoc полиморфизма а не какая нибудь другая реализация полиморфизма? В общем тема не раскрыта.

TDrive ★★★★★ ()
Ответ на: комментарий от ilammy

определить в общем случае, является ли один предикат подтипом другого

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

komputikisto ()
Ответ на: комментарий от ilammy

одновременно (конъюнктивные объединения) или одного из указанных типов (дизъюнктивные объединения)

Хотя, да так будет логичнее. Но предикаты тоже должны быть частью этой системы, ИМХО.

komputikisto ()
Ответ на: комментарий от TDrive

Объясните, если не сложно, почему Smalltalk - ad hoc полиморфизм?

Потому что это не тот полиморфизм, о котором в Си++.

Представьте себе функцию f(x). Вычисление этой функции моделирует вызов метода f у объекта x. Ad hoc полиморфизм — это когда поведение функции описывается отдельно для каждого поддерживаемого типа объектов, потому и ad hoc. Параметрический полиморфизм — это когда функция описывается в одном месте и автомагически работает для всех поддерживаемых типов. В случае ad hoc полиморфизма поддерживаемые типы определяются не самой функцией, а её специализациями. В случае параметрического же — описание функции определяет требования к поддерживаемым типам. Пример из того же Си++: ad hoc — виртуальные методы; параметрический — шаблонные функции.

И каким образом полиморфизм помогает настраивать существующий код без его изменения?

Если есть функция g(y), которая определяется через f(x), то поведением g(y) можно управлять без изменения её определения, лишь вводя новые специализации f(x).

ilammy ★★★ ()
Ответ на: комментарий от ilammy

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

А если функция определяется у родительского класса и наследуется всеми потомками разве это не оно?

TDrive ★★★★★ ()
Ответ на: комментарий от ilammy

Если есть функция g(y), которая определяется через f(x), то поведением g(y) можно управлять без изменения её определения, лишь вводя новые специализации f(x).

С параметрическим полиморфизмом такого не получится?

TDrive ★★★★★ ()
Ответ на: комментарий от TDrive

И каким образом полиморфизм помогает настраивать существующий код без его изменения?

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

komputikisto ()
Ответ на: комментарий от komputikisto

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

TDrive ★★★★★ ()
Ответ на: комментарий от komputikisto

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

Вот это-то и не нравится. Типы ведь определяются как раз для того, чтобы о них думала машина, а не человек всё проверял и кастовал вручную. В идеале, чтобы машина выводила общие закономерности без собственно исполнения программы.

Хотя, да так будет логичнее. Но предикаты тоже должны быть частью этой системы, ИМХО.

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

ilammy ★★★ ()
Ответ на: комментарий от TDrive

А если функция определяется у родительского класса и наследуется всеми потомками разве это не оно?

Если потомки не могут её переопределять, то в общем-то да. А вот переопределение — это и есть ad hoc специализация для класса потомка, которая не была прописана в определении предка.

С параметрическим полиморфизмом такого не получится?

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

Если взять этот пример с f(x) и g(y), то при необходимости поменять поведение g(y) в случае ad hoc полиморфизма мы можем взять и поменять g(y) напрямую, а в случае параметрического — должны поменять поведение f(x) так, чтобы g(y) имела желаемое поведение.

ilammy ★★★ ()
Ответ на: комментарий от komputikisto

Но проблема в том, что арифметические операторы бинарны, а сообщение посылается только одному объекту

О, да ты вообще нулевой в ООП, вони только от тебя много

Number.prototype.plus=function(x){return this+x}
1..plus(2) // --> 3
Нужен клоз.

anonymous ()
Ответ на: комментарий от anonymous

А теперь определи коммутативное сложение двух различных типов.

ilammy ★★★ ()
Ответ на: комментарий от TDrive

почему ты выделяешь именно ad hoc полиморфизм

Параметрический полиморфизм — тривиальное свойство присущее динамическим языкам без каких-либо сложностей. Над обеспечением ad hoc полиморфизма нужно работать и человечество не сразу додумалось до идеи обобщенных функций. Хотя они в той или иной форме возникали до CLOS (можно назвать сейчас вообще никому не известный язык ECL и диалект Лиспа T с его псевдо-обобщенными функциями), но именно CLOS обобщил весь накопившийся опыт работы с ОО языками.

komputikisto ()
Ответ на: комментарий от ilammy

Если потомки не могут её переопределять, то в общем-то да. А вот переопределение — это и есть ad hoc специализация для класса потомка, которая не была прописана в определении предка.

То есть параметрический полиморфизм исключает переопределение функций?

TDrive ★★★★★ ()
Ответ на: комментарий от ilammy

Можно это сделать. Но напомню, что говорил поциэнт:

Но проблема в том, что арифметические операторы бинарны, а сообщение посылается только одному объекту (одному аргументу). Вот тут и нужен CLOS.

anonymous ()
Ответ на: комментарий от anonymous

Можно это сделать.

python
>>> dir(42)
['__add__', '__radd__', '__mul__', '__rmul__', ... ]

Ты серьезно думаешь, что я не знаю для чего нужны radd и rmul?

komputikisto ()
Ответ на: комментарий от anonymous

Можно это сделать.

Да никто ж не спорит, что можно. Тьюринг-полные языки, как-никак. Но речь то о multiple dispatch.

Вот как такая функция будет выглядеть на прототипах?

func(Number, String) → «1»
func(String, Number) → «1»
func(Number, Boolean) → «2»
func(Boolean, Number) → «2»

Как куча ифов, которые надо прописывать лично в Number, String, Boolean, и всех остальных типах, по которым нужно специализироваться. Тогда как в CLOS все эти ифы собирает в себе сама func.

ilammy ★★★ ()
Ответ на: комментарий от komputikisto

Я серьезно думаю, что ты не понимаешь того о чем пытаешься рассуждать.

anonymous ()
Ответ на: комментарий от ilammy

Вот как такая функция будет выглядеть на прототипах?

func(Number, String) → «1»
func(String, Number) → «1»
func(Number, Boolean) → «2»
func(Boolean, Number) → «2»

А где ещё пять случаев?

Laz ★★★★ ()
Ответ на: комментарий от ilammy

Object.prototype.dyspatch=function(x){
   switch((typeof this.valueOf())+(typeof x)){
      case "numberstring": return 1 
      case "stringnumber": return 1
      case "numberboolean": return 2
      case "booleannumber": return 2
   }
}

1..dyspatch("foo").show()
"foo".dyspatch(1).show()
3..dyspatch(false).show()
false.dyspatch(1).show()

//  1
//  1
//  2
//  2

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

anonymous ()

Полностью с вами согласен, коллега

хоть и не читал ОП

niemand ()
Ответ на: комментарий от komputikisto

аналог CLOS для тикля есть

Не совсем, но XOTcl и его продолжатель NX

buddhist ★★★★★ ()
Ответ на: комментарий от anonymous

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

Я не разбираюсь в JavaScript, но мне кажется, что в этот switch новые ветки добавлять невозможно. Таким образом твой код не реализует ad hoc полиморфизм, т.к. не расширяем. Авторы Python хоть предусмотрели методы __radd__ и __rmul__ для обеспечения диспетчеризации по второму аргументу, а у тебя ничего. И CLOS без MOP — вещь очень простая и в реализации и концептуально.

komputikisto ()
Ответ на: комментарий от komputikisto

не расширяем

Об этом изначально речь не шла


rules={
   numberstring: 1,
   stringnumber: 1,
   numberboolean: 2,
   booleannumber: 2
}
Object.prototype.dyspatch=function(x){
   return rules[((typeof this.valueOf())+typeof x)]
}

rules.functionstring=10

console.log(
1..dyspatch("foo"),
"foo".dyspatch(1),
3..dyspatch(false),
false.dyspatch(1),
function(){}.dyspatch("huy")
)

//  1 1 2 2 10

Python хоть предусмотрели

пистон говно. Это жаба для веба. че ты тут говном разбрасываешься. В тредах про правильное ООП упоминание этого говна недопустимо.

J-yes-sir ()
Ответ на: комментарий от J-yes-sir

Замечательно! Вот ты и реализовал небольшую часть CLOS. Десятое правило Гринспена:

Любая достаточно сложная программа на Си или Фортране содержит заново написанную, неспецифицированную, глючную и медленную реализацию половины языка Common Lisp.

komputikisto ()
Ответ на: комментарий от J-yes-sir
person("Jack").age().oldEnoughToWatchPornFilm(showPorn("Big Dicks in New Jersey"))

А во втором варианте задолбаешься скобки писать, вот и вся разница. А остальное все — это демагогия.

(((person "jack") age)
 (oldEnoughToWatchPornFilm (showPorn "Big Dicks in New Jersey")))

И то верно. В одном случае их 8, а в другом --- целых 10. Большая разница.

ugoday ★★★★★ ()
Ответ на: комментарий от komputikisto

диспетчеризации по второму аргументу,

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


show=function(o){return this.one||o.one}
o={one: 1}
o.show=show

console.log(
o.show(),
show(o)
)

J-yes-sir ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.