войти зарегистрироваться

Io Language: Объектная система

Введение

Продолжая писать про io, совершенно необходимо остановиться отдельно на устройстве объектной системы этого чудесатого езычка. Главная проблема в том, что даже если вы «знаете» ООП, вполне может сложиться ситуация, что вы просто не поймете, как оно устроено в io. Сейчас под ООП почему-то подразумевается модель Java, чаще всего. C++ нельзя назвать объектно-ориентированным языком, потому что он язык поддерживающий парадигму ООП, но это не его основная парадигма. Java больше подходит под гордое звание Ъ-ООП языка, но вот беда, ООП диктуемое Java-like языками довольно извращено. Изначально принципы ООП зарождались в Smalltalk'е и там все выглядело несколько иначе, объекты общались друг с другом не посредством вызова методов, а посредством передачи друг другу сообщений, мне до сих пор странно, почему же от этой модели ушли, ведь такое построение позволяет ввести прозрачную параллельность в язык без костылей. Посмотрите на нынешние круто параллелящиеся языки, тот же Erlang например, там все сделано через сообщения. Опять же нагородили огородов из RPC, COM и прочего dbus'а. А ведь как все хорошо начиналось.

Прототипное ООП

Мы как-то привыкли к тому, что ООП строится на классах. Класс — описание типа, объект — экземпляр класса. В целом такая модель очень хорошо себя зарекомендовала в нелегком деле разделения логики и данных, однако внесла дополнительную путаницу в программы. Вам нужно держать в голове все экземпляры класса, а так же сам класс. Вопрос, конечно, спорный и желания наезжать на классическую модель «класс-объект» у меня нет, но рассказать о преимуществах прототипов все-таки хочется.
Глянем на прототипы, в двух словах я описывал структуру в первой статье, но на всяких случай напомню. Прототипность языка заключается в том, что напрочь отсутствуют такие понятия как класс, экземпляр класса и прочее связанное с разделением декларации типа и данных, в io есть только объекты. Объект всегда имеет свой инстанс (как это по русски будет?), получить новый объект можно клонировав старый и изменив его свойства, добавив/убрав из него нужные слоты. Таким образом мы убиваем сразу целый косяк грызунов: у нас есть активный объект, свойства/методы которого доступны для использования и он же является прототипом («декларацией класса») для своих потомков. Очень отдаленно (ну о-о-о-очень отдаленно) такая структура напоминает статические классы.

Объекты которые есть всегда

Сразу после запуска интерпретатора вам доступны несколько «глобальных» объектов, пожалуй самые важные из них вот эти:
  • Lobby
  • Object
Первый объект хранит в себе рантайм и является, так сказать, почвой под ногами у выполняемого кода. Второй — базовый объект для клонирования, чаще всего первыми строками исходника на io бывают комментарии «SomeObject := Object clone». То есть даже когда вы пишите вроде бы не «объектно», вы все-равно пишете объектно. Например код:
factorial := method(number,
  if(number == 0,
    1,
    number * factorial(number - 1)
  )
)
factorial(5) print
Создает вовсе не функцию факториал, он создает слот факториал в объекте Lobby. Lobby является контекстным объектом, пока явно контекст не переключается на другой объект.

Немножко вуду

Объекты в io интроспективны, то есть вы можете копаться у них в кишках как вашей извращенной натуре только захочется, если у вас есть какой-то непонятный объект, вы всегда можете посмотреть чего у него внутре (печеньки неонка!).
Lobby slotNames print
Это уже началось метапрограммирование, тема отдельной статьи, пока я только упомяну то, что вы можете «на лету» делать с объектами такие штуки, которые ни в одном немецком фильме не показывают.

Модульность

Может это и тема отдельной статьи, но пробежаться надо в любом случае. Конечно io модульный язык, иначе не могло быть просто потому, что не могло быть. Причем модульность в io сделана крайне прикольным образом, тут нет никаких import/include/require_once, тут все проще. Есть некий модуль Z_Importer, который загружается вместе с интерпретатором в память, как только вы пытаетесь использовать какой-либо объект не входящий в лексический обзор текущего файла, этот самый модуль ломится искать файл с именем объекта. Сначала в текущем каталоге, потом по каталогам библиотек (указываемым методом addSearchPath). Допустим классы Mushroom, Lenin и Man из первой статьи лежат в отдельных файлах. Как накормить мужика?
Mushroom //Достали грибочек из  Mushroom.io
Lenin    //Достали Владимирильича из Lenin.io
Man      //Достали мужыка из Man.io

Man eat(Mushroom)
Man state println
По-моему это самый дзэнский импортер из всех, которые я видел (:

Ну и все, пожалуй

Вроде бы это все, что нужно знать о объектной модели io, главное помнить про сообщения, но это уже совсем другая история (:

Ссылки

Если кому-то интересно что, где когда и как, можно почитать референс по io: http://iolanguage.com/scm/git/checkout/Io/docs/IoReference.html
А еще мы вчера наколбасили русскоязычный irc-канал про io: #io-ru@FreeNode, ждем заинтересовавшихся (:

P.S. У вас тут, между прочим, в минусах сидит один из знатоков io (А в догонку еще и разработчик StrokeDB) — посмотреть профиль oleganza, я хочу попросить, что бы он поправил меня или дополнил, если я где-то ошибся или чего-то недоговорил.

(Из моего блога)

комментарии (72)

  • "Объект всегда имеет свой инстанс (как это по русски будет?)" — масло всегда масляное?
    Не совсем ясно, что здесь подразумевается под "инстанс".
    • Тавтология с точки зрения «традиционного» ООП, да. Инстанс — объект всегда «материален», присутствует в рантайме, в прочих ооп языках инстанс появляется только в момент new.
      Я написал слово «инстанс» что бы было проще понять людям не сталкивавшимся с прототипным ооп, но видимо только запутал (:
      • Тот же жаваскрипт — язык с прототипным ООП, так что, я думаю, многие сталкивались. :)
      • Проще говоря - объект есть, его не может не быть.
    • очевидно "инстанс" — экземпляр
      • Про то что "инстанс" — это экземпляр класса, то есть объект, в Жаваподобных ООП языках, я знаю.
        Я же интересовался — что _здесь_ подразумевалось под "инстанс"ом объекта.
        • Объект, он и есть инстанс. То бишь, абстрактных объектов, не являющихся инстансами, вообще не существует. В данном контексте понятия "объект" и "инстанс" обозначают одну и туже сущность.
          • Тогда фразу:
            "Объект всегда имеет свой инстанс (как это по русски будет?)"
            надо менять, т.к. см. первый комментарий. :)
            • Да уж, это не так просто сформулировать. В одном учебнике по Ruby я прочитал:

              "Просто помните, что класс, это объект, а объект, это класс. Не пытайтесь это понять, а просто повторяйте, как мантру".

              То есть, авторы даже не пытались что-то объяснить, а может просто прикалывались. Rubистам вообще присуще чувство юмора.
              • Э-э-э... Прошу прощения за оффтопик, но авторы того учебника сильно обманули во фразе "объект - это класс". "Класс - это объект" - тут да, всё правильно, а вот обратное в корне неверно.
                • Применительно к руби верно и обратное. Обявление класса в руби так же является объектом.
                  • Ещё раз: фраза "класс - это объект" верна (для Ruby), фраза "объект - это класс" ошибочна.

                    ontopic: А вот для прототипных языков, кстати, фразу "объект - это класс" в некотором приближении можно считать верной.
                    • Неправильно вас понял :)
                    • может, они имели в виду, что Object тоже класс? ;)
  • " добавив/убрав из него нужны слоты"
    это вы специально сократили слово нужные?
    • Опечатался, спасибо, поправлю.
  • Инстанс - сущность, экземпляр.
    Спасибо за статью, будем расширять кругозор.
  • В принципе забавно, но интересно узнать практическое применение, где удобно выгодно, быстрее. Как и почему)
    • Практическое применение довольно обширно, в общем-то «common purpose» язык, выгодно везде где есть желание/необходимость делать сложные замороченные мегаизвраты метапрограммированием без потери структурности кода, написание кодогенераторов, DSL, парсеров, etc. Быстрее за счет нативной конкурентности на уровне потоков.
      • Термин "замороченные мегоизвраты" мне уже нравится)
        Найти бы свободное время что бы поиграться, эх. Сам код мне нравится.
  • продолжайте, очень интересно
    • Спасибо, еще минимум две статьи будет.
      • даешь пять! ;)
  • Заинтересовал язык...
    Как кто-то здесь уже сказал: найти бы время поиграться с ним!
  • Ээ а как синхронные сообщения "позволяют ввести прозрачную параллельность в язык без костылей".
    • Кто сказал синхронные? (:
      Вообще сделать асинхронными сообщения по-моему проще, чем вызов методов.
  • Пара вопросов.
    1)"объекты общались друг с другом не посредством вызова методов, а посредством передачи друг другу сообщений"

    Чем посылка сообщений отличается от вызова метода?

    2)"этот самый модуль ломится искать файл с именем класса."

    Что делать с классами с одинаковыми названиями?
    • 1. В общих чертах, внешне --- ничем. Внутреннее это другой подход (по-моему интуитивно понятно в чем разница) Если интересно почитайте статью про Smalltalk в википедии, там, вроде бы неплохо описано.
      2. Классов с одинаковыми названиями в рамках одной сессии работы интерпретатора не бывает. (Я кстати опечатался, классов вообще не бывает, бывают только объекты)
    • Про методы/сообщения - http://habrahabr.ru/blog/crazydev/45332.…
      • Если это действительно все различие то непонятно как это влияет на прозрачность параллельности, да и автору было бы неплохо привести пример реализации отличительной фичи.
        • Что, действительно непонятно? А если reciever object у меня на другой ноде? А если он динамически изменяется? Все еще непонятно?
          Сообщение посылается удаленному объекту, а метод вызывается. Система сообщений по своей природе распределенная, сообщения проще сделать асинхронными и организовать поверх них модель актеров. Посмотрите вот эту презентацию Стива Декорте, что бы понять о чем я: http://iolanguage.com/docs/talks/2006-10…
    • По поводу вопроса №1.

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

      Обрабатывать сообщение или отклонить - решает принимающая сторона. Говоря в терминах класс-ориентированного ООП, принимающая сторона имеет динамический интерфейс.

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

      На тему сообщений есть интересная статья (точное название не помню, из журнала "Byte" за 1981 год, найти можно на Smalltalk.ru). В ней описаны цели создания Smalltalk, как системы, основанной на сообщениях и преимущества такого подхода.

      Асинхронность же тут ни при чём. И синхронная и асинхронная обработка сообщений примерно одинаково реализуются и в случае с вызовами, и в случае с сообщениями. По этим же причинам, мало общего с циклом сообщений Win32 API.
  • Мне вот интересно, зачем называть привычные всем вещи другими названиями? Чтобы выделиться? Во многих языках есть глобальный объект/класс System или Runtime, зачем именовать его Lobby? Это нифига не интуитивно
    • Ну вот сколько я пишу на io мне еще ни разу не приходилось обращаться к Lobby по имени. Почему нет?
  • Никакой принципиальной разницы между вызовом методов и отправкой сообщений нет. И то, и другое — суть _вызов_ некоторого кода, разница лишь в _оформлении_ оного вызова. Физическая же реализация может быть какой угодно.

    Саму идею отправки сообщений многие проходили еще при изучении win32 API — функция SendMessage отправляла сообщение синхронно, а PostMessage асинхронно. Ничего положительного, кроме лишнего геморроя, такая система не давала, но зато позволяла писать на необъектно-ориентированных языках типа C. В нормальных же языках приходилось создавать обертки над этой системой (VCL).

    Что же касается "классических" ОО-языков, типа C++, Object Pascal или Java, то нет никаких причин, кроме нежелания их разработчиков, реализовать асинхронный вызов методов. Метод, который следует вызывать асинхронно, можно отмечать модификатором при декларировании, например так:

    class TMyClass
    async procedure DoSmth(Param: Integer {любые другие параметры});
    end;

    При вызове такого метода происходит помещение параметров в очередь и немедленный возврат. Дальше код метода занимается обработкой переданных в очередь параметров либо последовательно, либо параллельно (в отдельных потоках), либо последовательно-параллельно через пул потоков. Сам по себе оный объект может находиться где угодно.

    Таким образом, есть все основания полагать, что изыскания в области принципиальной разницы между вызовом методов и отправкой сообщений есть ни что иное, как создание "научной новизны" в области Computer Science какого-либо PhD student или еще кого.
    • Здраво, но по факту мы имеем кривую реализацию вызова и хотя бы отдаленно прямую реализацию посылки сообщений.
      Сообщения, по-моему, более человекоподобны, все-таки.
      • Не вижу здесь никакой кривоты. Реализация аналогична реализации посылки синхронных/асинхронных сообщений, именно в силу отсутствия принципиальной разницы. Можно даже сказать, что методы — это своего рода красивая обертка для отправки сообщений :-)

        Тем более, тут еще встает вопрос о том, какой именно конструкцией реализовывать отправку сообщений. Если в стиле win23 API — SendMessage(Instance, Message, WParam, LParam) — то ничего человечного тут нет. Куда более человечным будет Instance.Message(WParam, LParam).

        Что же касается конструкций описываемого Вами языка, то ничем иным, кроме желания у его автора выпендриться, я не могу объяснить замену "." или на худой конец "->" пробелами. Ибо как Вы совершенно верно показали в своей предыдущей заметке по теме, никакой принципиальной разницы между Instance print и Instance.print не существует (в том числе и в плане человечности). И то, и другое — это вызов некоего кода, который делает что-то с объектом Instance. Как именно называть это — "вызов метода" или "отправка сообщения" — совершенно неважно, ибо ПО СУТИ действие одно — выполнение некоторого кода в контексте объекта Instance.

        Если же говорить о человекоподобности, то человекоподобным будет print "Hello world", а вовсе не наоборот ;-)
        • Заменя . -> и прочего мусора на " " обусловлена двумя факторами: 1. Наследие Smalltalk'а (А там именно так) 2. Стремление максимально упростить синтаксис.
          А в остальном я с вами даже соглашусь, хотя вы сами пишете о том, что вызов метода --- обертка над асинхронным сообщением, тут мы минуя обертку получаем что хотим и все дела (:
          • Это ТОЖЕ обертка, просто оформленная иначе и ничего более :-)
    • Разница есть. Если в "классическом" ооп-языке у объекта вызывается несуществующий метод, то код либо не собирается, либо падает в рантайме. А если язык отсылает именно сообщение, то у объекта-получателя есть возможность это сообщение обработать (независимо от того, есть у него соответствующий метод или нет). Например, таким образом можно сделать элегантную реализацию прокси (все сообщения, пришедшие объекту Б, отправляем объекту С). Или вот такой syntax sugar (ruby):

      User.find_by_title_and_author_id("First post", @author);

      Тут класс User получает сообщение "find_by_title_and_author_id", парсит его, выдирает поля для поиска (title и author_id) и делает запрос к базе. Такая вот особая динамическая магия.
      • Ничто не мешает добавить линковку несуществующего метода на некий универсальный вида DispatchUnknownMethod(const Name: String; const Params: array of const);
        Было бы желание у разработчика компилятора.

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

          Зачастую разница в реализации - это принципиальная разница. Мы, конечно, можем научить Паскаль универсальному диспатчингу сообщений, но какой в этом смысл? Гораздо лучше, когда разные языки решают предлагают разные механизмы для решения наших задач.
          • разные языки решают предлагают
          • Скорее всего, это действительно лучше. Но раздражает обилие попугайского синтаксиса в таких языках. Почему-то каждый автор стремиться добавить какой-то свой особенный изврат, не имеющий ничего общего с наиболее распространенными вариантами.

            Что же касается Object Pascal, то в случае, когда нужен асинхронный вызов метода (я по прежнему настаиваю на том, что нет принципиальной разницы между методами и сообщениями), реализовать это несложно даже без нативной поддержки. Просто код разбивается на две части — внешняя часть помещает параметры в очередь, внутренняя их разгребает.
            • Если бы каждый автор не добавлял своих "особенных извратов", у Вас бы сейчас не было столь любимых стрелочек и точечек. Посмотрите на Smalltalk-80, такой синтаксис у первого оо-языка. Вообще, "прогресс не возможен без отклонений от нормы (с)", поэтому чем разнообразнее языки идейно и синтаксически, тем больше вероятность найти для себя подходящий инструмент :)
              • Насчет стрелочек не соглашусь. Мне нравится pascal-style синтаксис.

                Что же касается разнообразия, то достало после php писать в delphi что-то вида for(), а после delphi в php писать for i := 0 to :-)))
        • Принципиальная разница в использовании позднего связывания. "Сообщения" — это просто вызов метода, да, но обязательно поиск обработчика должен происходить в рантайме.
          • Для позднего связывания есть dispinterface :-)
      • в пхп есть волшебная функция __call которая вызывается, в случае отсутствия у объекта нужного метода.
        • В пхп много чего есть, только этим мало кто пользуется.
          • в пхп много чего есть только почти все через жопу
        • пхп-пхп, а в Perl есть AUTOLOAD!
    • Что касается реализации асинхронных методов в С++ и Object Pascal, то мне кажется, это вызвало бы больше вопросов, чем каких-то удобств. Поддержки многопоточности на уровне языка в этих языках нет, а асинхронный вызов подразумевает либо использование дополнительных потоков, либо передачу управления из основного потока в какие-то моменты времени в эти "помещённые в очередь вызовов" функции.

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

      А самое главное — если смотреть шире, то сообщения между объектами, это всего лишь частный случай паттерна проектирования event dispatcher/subscriber. Не очень понятно, чем этот паттерн лучше всех остальных, чтобы его реализовывать на уровне конструкций языка, когда его можно реализовать имеющимися средствами, имея в результате бОльший контроль над получившейся системой.
      • Согласен с Вашим высказыванием. Лично я отлично обхожусь без асинхронных вызовов методов в Object Pascal, ибо способов их реализовать есть несколько. Мое возражение касалось исключительно высказываний о принципиальной разнице (и преимуществе) т.н. концепции "сообщений" над методами.
  • Мне кажется, что рекурсивное вычисление факториала - не самый лучший способ.
    • Вам шашечки или ехать? Это сэмпл, самый простой который пришел мне в голову, когда я писал. Не слишком перегруженный, не слишком сложный.
    • Кстати, по теме:

      Number ! := method(self * (self - 1)!)
      0 ! := 1

      Io> 5!
      ==> 120

      (ахтунг тут: http://pastie.org/225449)
  • прототипное ооп - то ещё недоразумение. оно ещё годится к применению в смалтолке, потому как разработка для него происходит исключительно интерактивно в среде разработки. для более традиционных способов написания программ, состоящих и двух последовательных этапов: написание и интерпретация, - прототипное ооп не годится совершенно. всё-равно выделяются объекты, выполняющие функции классов (хранение общих для всех экземпляров свойств в одном месте). либо не выделяются и получается каша-малаша.

    опять же старая добрая проблема пространств имён. есть два модуля, каждый из них создаёт, объект Lobby.RSS, только один из них реализует rss1, а другой - rss2. а мы хотим иметь возможность в нашем приложении создать оба рсс-а.
    а вообще, автолоад - то ещё зло. если у нас есть два файла с одинаковым именем, но в разных директориях - начинается веселье с редактированием не того файла.
    простота и элегантность программы типа "привет мир" - ничто по сравнению с необходимостью отлаживать последствия такой вот самодеятельности.
    • Epic Fail. В Смолтоке небыло прототипного ооп.
      Вы когда-нибудь что-либо крупное на lisp'е писали? Я вот писал, знаете, ничего удобнее REPL еще не придумали для отладки, можете погуглить историю, как люди дебажили lisp код находящийся в глубоком космосе. А пространства имен можно запортачить и в java, нужно просто понимать что ты делаешь, вот и все.
      • боже упаси писать что-либо на лиспе, тем более крупное..
        гугль, вот, со мной согласен: http://www.google.com/search?ie=UTF-8&oe=UTF-8&sourceid=navclient&gfns=1&q=%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D1%8E%2C+%D0%BA%D0%B0%D0%BA+%D0%BB%D1%8E%D0%B4%D0%B8+%D0%B4%D0%B5%D0%B1%D0%B0%D0%B6%D0%B8%D0%BB%D0%B8+lisp+%D0%BA%D0%BE%D0%B4+%D0%BD%D0%B0%D1%85%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B9%D1%81%D1%8F+%D0%B2+%D0%B3%D0%BB%D1%83%D0%B1%D0%BE%D0%BA%D0%BE%D0%BC+%D0%BA%D0%BE%D1%81%D0%BC%D0%BE%D1%81%D0%B5

        а насчёт репла - смешно, да. из шэлла ковырять многовложенную функцию - занятие для настоящего любителя сэкса..
        • Чот я потерял мысль. При чем тут шелл? Вы вообще лисп в глаза видели? А ide для лиспа?
          • которую иде? не емакс случаем =)
          • шелл - это не только баш, если что...
        • И даже более впечатляющий пример удаленной отладки произошел в миссии NASA «Deep Space 1» в 1998 году. Через полгода после запуска космического корабля, небольшой код на Lisp должен был управлять космическим кораблем в течении двух дней для проведения серии экспериментов. Однако, неуловимая race condition в коде не была выявлена при тестировании на земле и была обнаружена уже в космосе. Когда ошибка была выявлена в космосе (100 миллионов миль от Земли) команда смогла произвести диагностику и исправление работающего кода, что позволило завершить эксперимент.

          (c) PCL (http://pcl.catap.ru/doku.php?id=pcl:%D1%…)
          • о, да, делать всё через задницу - это очень в духе лиспа =) вместо того, чтобы взять исходники/дамп_состояния, запустить на виртуальной машине и отдебажить, они предпочли трахаться с удалённой отладкой.
            • Вам-то виднее, ясен пень (:
        • какой-то гугол не постоянный...
          http://img-fotki.yandex.ru/get/26/sairi-na-tenshi.6/0_1142d_4299be4f_orig
  • спасибо за отличные статьи - с удовольствием читаю.
    решал себе поставить сабж, но не нашел его в репах дебиана :(

    вот неофициальный репозитарий сборок IO :

    http://www.mail-archive.com/debian-mentors@lists.debian.org/msg54241.html
    • Не за что, приходите, будет еще. А вот ставить io я бы рекомендовал из исходников. У меня eeeXubuntu собралось с полпинка.
      • А вот ставить io я бы рекомендовал из исходников.

        в ссылке, что я дал, как раз указан deb-src репозитарий
        т.е. ставится так:

        apt-get -b source iolanguage
        и потом dpkg -i iolanguage-bla-bla.deb
        • Ну обычно я качаю тарболл с официального сайта, или беру прямо из гита.
          Так оно как-то спокойнее (:
Только авторизованные пользователи могут оставлять комментарии. Авторизуйтесь, пожалуйста.