Введение
Продолжая писать про io, совершенно необходимо остановиться отдельно на устройстве объектной системы этого чудесатого езычка. Главная проблема в том, что даже если вы «знаете» ООП, вполне может сложиться ситуация, что вы просто не поймете, как оно устроено в io. Сейчас под ООП почему-то подразумевается модель Java, чаще всего. C++ нельзя назвать объектно-ориентированным языком, потому что он язык поддерживающий парадигму ООП, но это не его основная парадигма. Java больше подходит под гордое звание Ъ-ООП языка, но вот беда, ООП диктуемое Java-like языками довольно извращено. Изначально принципы ООП зарождались в Smalltalk'е и там все выглядело несколько иначе, объекты общались друг с другом не посредством вызова методов, а посредством передачи друг другу сообщений, мне до сих пор странно, почему же от этой модели ушли, ведь такое построение позволяет ввести прозрачную параллельность в язык без костылей. Посмотрите на нынешние круто параллелящиеся языки, тот же Erlang например, там все сделано через сообщения. Опять же нагородили огородов из RPC, COM и прочего dbus'а. А ведь как все хорошо начиналось.
Прототипное ООП
Мы как-то привыкли к тому, что ООП строится на классах. Класс — описание типа, объект — экземпляр класса. В целом такая модель очень хорошо себя зарекомендовала в нелегком деле разделения логики и данных, однако внесла дополнительную путаницу в программы. Вам нужно держать в голове все экземпляры класса, а так же сам класс. Вопрос, конечно, спорный и желания наезжать на классическую модель «класс-объект» у меня нет, но рассказать о преимуществах прототипов все-таки хочется.
Глянем на прототипы, в двух словах я описывал структуру в
первой статье, но на всяких случай напомню. Прототипность языка заключается в том, что напрочь отсутствуют такие понятия как класс, экземпляр класса и прочее связанное с разделением декларации типа и данных, в io есть только объекты. Объект всегда имеет свой инстанс (как это по русски будет?), получить новый объект можно клонировав старый и изменив его свойства, добавив/убрав из него нужные слоты. Таким образом мы убиваем сразу целый косяк грызунов: у нас есть активный объект, свойства/методы которого доступны для использования и он же является прототипом («декларацией класса») для своих потомков. Очень отдаленно (ну о-о-о-очень отдаленно) такая структура напоминает статические классы.
Объекты которые есть всегда
Сразу после запуска интерпретатора вам доступны несколько «глобальных» объектов, пожалуй самые важные из них вот эти:
Первый объект хранит в себе рантайм и является, так сказать, почвой под ногами у выполняемого кода. Второй — базовый объект для клонирования, чаще всего первыми строками исходника на 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)
Не совсем ясно, что здесь подразумевается под "инстанс".
Я написал слово «инстанс» что бы было проще понять людям не сталкивавшимся с прототипным ооп, но видимо только запутал (:
Я же интересовался что _здесь_ подразумевалось под "инстанс"ом объекта.
"Объект всегда имеет свой инстанс (как это по русски будет?)"
надо менять, т.к. см. первый комментарий. :)
"Просто помните, что класс, это объект, а объект, это класс. Не пытайтесь это понять, а просто повторяйте, как мантру".
То есть, авторы даже не пытались что-то объяснить, а может просто прикалывались. Rubистам вообще присуще чувство юмора.
ontopic: А вот для прототипных языков, кстати, фразу "объект - это класс" в некотором приближении можно считать верной.
это вы специально сократили слово нужные?
Спасибо за статью, будем расширять кругозор.
Найти бы свободное время что бы поиграться, эх. Сам код мне нравится.
Как кто-то здесь уже сказал: найти бы время поиграться с ним!
Вообще сделать асинхронными сообщения по-моему проще, чем вызов методов.
1)"объекты общались друг с другом не посредством вызова методов, а посредством передачи друг другу сообщений"
Чем посылка сообщений отличается от вызова метода?
2)"этот самый модуль ломится искать файл с именем класса."
Что делать с классами с одинаковыми названиями?
2. Классов с одинаковыми названиями в рамках одной сессии работы интерпретатора не бывает. (Я кстати опечатался, классов вообще не бывает, бывают только объекты)
Сообщение посылается удаленному объекту, а метод вызывается. Система сообщений по своей природе распределенная, сообщения проще сделать асинхронными и организовать поверх них модель актеров. Посмотрите вот эту презентацию Стива Декорте, что бы понять о чем я: http://iolanguage.com/docs/talks/2006-10…
В смысле вызова - ничем, а в смысле обработки - принципиально. В случае с отправкой сообщения - ни отправляющая, ни принимающая стороны не имеют информации друг о друге - в отличие от класс-ориентированного подхода, где интерфейсы объектов жёстко определяются интерфейсом класса.
Обрабатывать сообщение или отклонить - решает принимающая сторона. Говоря в терминах класс-ориентированного ООП, принимающая сторона имеет динамический интерфейс.
Такой подход понижает связность системы, а значит - делает её более устойчивой к изменениям, нежели чем при использовании статически типизированных классов. Во всяком случае, такую цель ставила себе команда XEROX Parc при реализации сообщений в Smalltalk.
На тему сообщений есть интересная статья (точное название не помню, из журнала "Byte" за 1981 год, найти можно на Smalltalk.ru). В ней описаны цели создания Smalltalk, как системы, основанной на сообщениях и преимущества такого подхода.
Асинхронность же тут ни при чём. И синхронная и асинхронная обработка сообщений примерно одинаково реализуются и в случае с вызовами, и в случае с сообщениями. По этим же причинам, мало общего с циклом сообщений Win32 API.
Саму идею отправки сообщений многие проходили еще при изучении 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", а вовсе не наоборот ;-)
А в остальном я с вами даже соглашусь, хотя вы сами пишете о том, что вызов метода --- обертка над асинхронным сообщением, тут мы минуя обертку получаем что хотим и все дела (:
User.find_by_title_and_author_id("First post", @author);
Тут класс User получает сообщение "find_by_title_and_author_id", парсит его, выдирает поля для поиска (title и author_id) и делает запрос к базе. Такая вот особая динамическая магия.
Было бы желание у разработчика компилятора.
Так что принципиальной разницы таки нет, есть разница в реализациях, да и то есть впечатление, что подобные фишки потенциально способны привести к сложнопонимаемому коду.
Зачастую разница в реализации - это принципиальная разница. Мы, конечно, можем научить Паскаль универсальному диспатчингу сообщений, но какой в этом смысл? Гораздо лучше, когда разные языки решают предлагают разные механизмы для решения наших задач.
решаютпредлагаютЧто же касается Object Pascal, то в случае, когда нужен асинхронный вызов метода (я по прежнему настаиваю на том, что нет принципиальной разницы между методами и сообщениями), реализовать это несложно даже без нативной поддержки. Просто код разбивается на две части внешняя часть помещает параметры в очередь, внутренняя их разгребает.
Что же касается разнообразия, то достало после php писать в delphi что-то вида for(), а после delphi в php писать for i := 0 to :-)))
Что это за потоки, какие это будут моменты времени, как всем этим управлять непонятно.
А самое главное если смотреть шире, то сообщения между объектами, это всего лишь частный случай паттерна проектирования event dispatcher/subscriber. Не очень понятно, чем этот паттерн лучше всех остальных, чтобы его реализовывать на уровне конструкций языка, когда его можно реализовать имеющимися средствами, имея в результате бОльший контроль над получившейся системой.
Number ! := method(self * (self - 1)!)
0 ! := 1
Io> 5!
==> 120
(ахтунг тут: http://pastie.org/225449)
опять же старая добрая проблема пространств имён. есть два модуля, каждый из них создаёт, объект Lobby.RSS, только один из них реализует rss1, а другой - rss2. а мы хотим иметь возможность в нашем приложении создать оба рсс-а.
а вообще, автолоад - то ещё зло. если у нас есть два файла с одинаковым именем, но в разных директориях - начинается веселье с редактированием не того файла.
простота и элегантность программы типа "привет мир" - ничто по сравнению с необходимостью отлаживать последствия такой вот самодеятельности.
Вы когда-нибудь что-либо крупное на 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
а насчёт репла - смешно, да. из шэлла ковырять многовложенную функцию - занятие для настоящего любителя сэкса..
(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
в ссылке, что я дал, как раз указан deb-src репозитарий
т.е. ставится так:
apt-get -b source iolanguage
и потом dpkg -i iolanguage-bla-bla.deb
Так оно как-то спокойнее (: