четверг, 4 декабря 2008 г.
http://twitter.com/nosmileface
ООП в языках
Опять про кодогенерацию. На этот небольшая заметка о том, почему поддержка ООП напрямую в языке не должна существовать.
Итак попорядку. Мне не нравится термин <эдакое> программирование, потому что я понимаю под программированием процесс превращения дизайна программы в непосредственно код. То есть существует некоторый дизайн-документ, где подробно описан каждый аспект программы и чтобы дизайн-документ стал непосредственно программой, нужно написать реализацию согласно этому дизайн-документу на конкретном языке программирования. Помоему часто программированием называют как процесс создания дизайна программы, её архитектуры, так и процесс непосредственно материализации этого дизайна в виде кода на конкретном языке программирования. Вероятней всего это случилось от того, что в древние времена роль дизайнера и программиста исполнял один человек или даже группа, но всё равно не стояла задача разделения таких понятий. Вообщем не столь важны причины, просто отмечу, что я не люблю называть программированием весь процесс создания программы. Я разделаю весь процесс на две части: дизайн и программирование.
Под дизайном я понимаю обдумывание и документирование (иногда в голове) принципов работы будущей программы в виде какой-либо системы символов и понятий (понятийного аппарата). Дизайн может быть функциональный, объектно-ориентированный или какой-либо другой. Именно методика дизайна определяет какими способами ведется продумывание и построение/создание дизайн-документа. Можно рассматривать проблему с точки зрения множества объектов, которые взаимодействуют друг с другом или же как некий набор функционала, который имеет свойство быть декомпозированным в некую модульную структуру. Что конкретно вы применяете не так важно, потому что дизайн дело тонкое и не простое, лучше всего в этом процессе полагаться на то, что называется чутьем. Интуиция должна подсказивать какое решение верное, а какое нет. Ну и это в последствии проверяется на практике. Каждый хороший дизайнер/архитектор обладает хорошим чутьем.
Второй этап написания программы - программирование. Это задача перевести понятийный аппарат дизайн-документа на язык программирования. На этом этапе основная задача для программиста (как исполнителя) максимально точно передать все абстракции и понятия не допуская больших погрешностей. Это важно, потому что если идти сложными путями, то код становится раздутым и невнятным, что замедляет дальнейшее продвижение в реализации программы.
Но конечно же эти два этапа последовательны только в идеале. Зачастую они прогрессируют параллельно. Части дизайна переписываются и изменения отражаются в коде программы. Процесс приобретает итеративный вид. Иными словами так чаще всего (если не всегда) случается на практике. Поэтому вероятно и обзывают всё это одним словом - программирование.
Однако, вернемся к тому, что я считаю важным эти процессы разделять. Несмотря на то, что процессы сильно взаимосвязанны и влияют друг на друга. Язык программирования накладывает ограничения на систему понятий дизайн-документа. Также берутся во внимание и аппаратные ограничения. И на сегодняшний день, даже методика построения дизайн-документов (ООД) находит отражение в конструкциях языков программирования. И пусть такая связь должна существовать, но основная идея в том, что язык программирования как средство второго этапа, не должен напрямую отражать методику первого этапа. К примеру язык должен иметь средства для выражения объектно-ориентированного дизайна, но эти средства не должны быть конкретно объектно-ориентированными.
Что я имею ввиду? Давайте посмотрим на примере. Пусть в терминах объектно-ориентированного дизайна существует некая абстракция - интерфейс Reader. А также существуют его реализации: ZipReader, MemoryReader и FileReader.
Давайте напишем реализацию на языке Си:
struct Reader;
struct ReaderInterface { /* it's like vtable */
void (*close)(struct Reader *reader);
int (*read)(struct Reader *reader, void *memory, size_t size);
};
struct Reader {
struct ReaderInterface *interface;
void *private_data;
};
/* inline functions */
inline void rclose(struct Reader *reader)
{
reader->interface->close(reader);
}
inline int rread(struct Reader *reader, void *memory, size_t size)
{
return (*reader->interface->read)(reader, memory, size);
}
/* now we're defining somewhere interfaces with their private_data
meaning and with their own interface table */
struct Reader *create_zip_reader(const char *zipfile, const char *file_in_zip);
struct Reader *create_file_reader(const char *filename);
struct Reader *create_memory_reader(const void *ptr, size_t size);
/* and the usage */
void usage_function()
{
struct Reader *r = create_zip_reader("archive.zip", "file/in/archive.txt");
if (!r) {
error();
return;
}
int a = 0, b = 0, c = 0;
rread(r, &a, sizeof(int));
rread(r, &b, sizeof(int));
rread(r, &c, sizeof(int));
rclose(r);
}
А теперь напишем то же самое на С++:
struct Reader {
virtual ~Reader() {} // good C++ style, virtual destructor in interface
// virtual void close() = 0;
// no close() function, closing on delete
virtual int read(void *memory, size_t size) = 0;
};
Reader *create_zip_reader(const char *zipfile, const char *file_in_zip);
Reader *create_file_reader(const char *filename);
Reader *create_memory_reader(const void *ptr, size_t size);
void usage_function()
{
Reader *r = create_zip_reader("archive.zip", "file/in/archive.txt");
if (!r) {
error();
return;
}
int a = 0, b = 0, c = 0;
r->read(&a, sizeof(int));
r->read(&b, sizeof(int));
r->read(&c, sizeof(int));
delete r;
}
Как видно и на том и на другому языке можно реализовать такую конструкцию. Несмотря на некоторые затраты текста при написании реализации (больше букв), использование имеет примерно одинаковый вид. И это всё ценой поддержки в С++ конкретных конструкций одной методики дизайна. И всё бы было прекрасно, если бы не ограничения.
Каждая конкретная реализация методики полюбому имеет свои ограничения. А что если я захочу так, как делать нельзя? Приходится думать как обойти проблему в рамках поддержки этих конструкций в языке и если не получается, то мы спускаемся на уровень языка Си, где вообще никаких средств для реализации тех или иных идей из ООД нет. Или другой случай, когда появляется новая методика и её невозможно выразить в терминах старой методики, не переписывать же язык! Поэтому я считаю, что каждая конкретная реализация методики не нужна, ибо имеет слишком узкую область применения. А что если попробовать реализовать поддержку методик ООД, без конкретики. Допустим предложить некий синтаксис для непосредственных манипуляций с vtable или ещё что-нибудь достаточно общее, но не вводить понятия класс, объект и наследование. Впрочем надо сказать, что и самого языка Си уже достаточно, т.к. на примере выше можно чётко видеть, что простые inline функции почти решают проблемы нечитаемого вида обращений к виртуальной таблице.
Впрочем, возможно я погорячился и поддержка ООД действительно не может иметь никаких иных удобных для использования форм. Да и в конце концов все средства ООД в основном в С++ это операции с virtual table путем формирования иерархий классов. Возможно это и не так критично для языка. Но что однозначно я бы покритиковал, это языки в которых "всё является объектом". Это неправильный подход к формированию средств кодогенерации однозначно.
Но если же отойти от примера с ООД и вспомнить про другие техники, начать мыслить обобщенно и пойти по этому направлению. В голове начинают всплывать всяческие страшные картины языка в котором можно модифицировать синтаксис и придумывать новые конструкции и/или модифицировать уже существующие меняя их семантику. И рядом тут ещё метапрограммирование где-то стоит. Но стоит взглянуть на современные языки типа Nemerle или D, то оказывается, что мысли не так далеки от правды. В этих языках уже можно определять новые синтаксические конструкции и представлять свою семантику в виде макросов, которые оперируют с AST (abstract syntax tree). Поживем - увидим. Может именно это направление и будет ключевым в ближайшие 10-20 лет развития средств кодогенерации. Язык с динамическим синтаксисом и плавающей семантикой. Как вам такое?
понедельник, 1 декабря 2008 г.
Шаблоны и библиотеки
пятница, 28 ноября 2008 г.
Пояснения
Перечитав свой пост про эволюцию средств кодогенерации, я понял, что писать длинные и содержательные тексты явно занятие не для меня. И тем не менее, я хочу донести идею до конечного пользователя так сказать. Поэтому держите список ключевых моментов в виде отдельных мыслей:
- Весь рассказ про историю говорит о том, что развитие средств кодогенерации идет в неверном направлении уже 20-30 лет. Программисты упустили из виду сущность того, чем они занимаются.
- Всё, что я сказал про ООП говорит о том, что влияние ООП на языки программирования преувеличено. Термин ООП - не соответствует реальности. Существует только объектно-ориентированный дизайн.
- Обзор некоторых особенностей языков показывает, что не всё так плохо. Развитие средств кодогенерации наблюдается, но в темпах значительно ниже возможных.
- Вероятно причина всему - сильная коммерциализация явления "программирование", а также дурное влияние методик дизайна и архитектуры на программистов (бедные).
- Тут можно почитать схожие мысли. Идея в том, что вся эволюция в языках программирования последние 20-30 лет суетится вокруг именно средств дизайна и архитектуры. Средства же кодогенерации почти не развиваются.
- При осознании всего процесса эволюции и со взглядом на всё это снаружи, становится понятно, что не так и что можно поделать. Например: переписать препроцессор языка Си, переосмыслить вид представления поддержки объектно-ориентированного дизайна в языках.
- Дизайн/архитектура ПО и собственно программирование это разные процессы, хотя часто и происходят одновременно, параллельно. Оба процесса равнозначно важны и развивать нужно их методику в равной степени.
- Полезно остановиться и освободится от жизненной рутины и взглянуть на всё, что ты делаешь вне процесса/системы. Такие моменты дают бесценное понимание некоторых вещей. :-)
среда, 26 ноября 2008 г.
Эволюция языков программирования
Честно говоря, на такую тему можно написать работу размеров от небольшого эссе и до размеров средней книги. Тем не менее, я попытаюсь уместить всё в один пост блога и не утомить читателей чтением. Впрочем, это невозможно конечно же, но попытка обещает быть интересной. Поэтому приготовьтесь к чтению и вперед.
Заранее предупреждаю, я не имею полного набора фактов относительно истории развития языков программирования. Я не сведущ относительного того кто, когда и главное каких недалал поделок, называя их "языками программирования" и что уж скрывать, меня это не особо интересует. На данный момент времени понятно, что существует ряд популярных языков программирования в которых миллионы мух (вы уж простите, что я так о программистах, конечно же вы самые умные и интеллектуальные и <вставьте ваш комплимент>, но это звучит символично) что-то эдакое нашли. И опираясь на то, что я вижу сейчас и то, что было когда-то (немножко-то истории я всё же знаю), я сделаю свои выводы. И пожалуйста, не подумайте, что я ярый приверженец какой-то одной идеи будь то ООП или функциональщина. Я скорее пытаюсь не вдаваясь в термины/понятия парадигм поделиться своим представлением относительно явления "программирование".
Дженерейшн намба уан. Или его отсутствие.
Начиналось всё с железа конечно же. Были железяки какие-то и люди придумали, что было бы неплохо заставить их делать разные вещи. Первые железяки наверно и программировались по железному - куча рычажков, переключаем их в разное положение и получаем разные результаты работы. Прекрасно! Но хотелось большего. Вероятно в будущем появлялись ещё всякого рода извращения с перфокартами и какими-нибудь вариантами промежуточной памяти (регистры). Вообщем, это нас не сильно интересует, поэтому я это оставлю для тех, кого интересуют факты истории. Время шло и люди родили быстрые машины, которые умеют выполнять вполне определенный набор команд и назвали их CPU - процессор тобишь. Всё было круто и прекрасно, но однажды настал вероятно такой момент, когда запоминать все команды в циферках было сложновато. Уж не говоря о процессе написания программ.
Поколение некст или намба ту.
И тут появился первый язык программирования, если его можно так назвать - assembly language. Главное, на что стоит обратить внимание - это появление стадии трансляции из символьного представления машинных команд в непосредственно машинные. Ещё раз - главное это сама стадия, а не что она делает. Т.е. человек в то время открыл для себя важную штуку - машинный код он писать нифига не способен и появилась потребность в средствах кодогенерации.
Я немного отойду от главной темы и немного углублюсь в абстрактное понимание процесса программирования. Что это такое по сути? Мы пишем программы на любимых языках и формошлепим иногда, рисуем какие-то GUI элементы, подготавливаем какие-то наборы данных. По сути я называю этот процесс одним словом - кодогенерация. Т.е. если мы пишем в Си статический массив - это кодогенерация, если мы пишем функцию для рекурсивной обработки сложной древовидной структуры - это кодогенерация. Ключевая фишка тут это то, что данные и код это одно и то же. Рисуем картинку для программы - кодогенерация. Да, я понимаю звучит не важно, просто запомните пожалуйста, что я называю это так. Когда нужно будет различить, что я имею ввиду, я буду упоминать кодогенерацию данных или кодогенерацию исполняемого кода :)
Так вот, ассемблер - первое средство кодогенерации. Понятно дело, что оно не особо сильно ушло от машинного одномерного представления (память имеет одномерный вид) и поэтому писать на нём всё-таки сложновато. Да и к тому же ассемблер привязан к определенному процессору, т.к. использует вполне прямое символьное представление команд вполне конкретного процессора. Однако, и тут произошло не мало интересного. Появляются переменные, появляются макросы (препроцессор), появляются другие инструменты позволяющие человеку более внятно и компактно выражать какой же он хочет код сгенерировать.
Третье поколение. Революция.
Следующим шагом в развитии средств кодогенерации (читайте языков программирования) является структуризация кода. Кода в широком смысле этого слова, т.е. данных и исполняемого кода. Функция становится основным строительным блоком исполняемого кода, структура становится тем же основным строительным блоком данных. Становится ясно, что логика процессоров вполне похожа и можно создать некий абстрактный вариант команд процессора, который можно преобразовывать в машинный код для различных архитектур. Как видно, куча средств кодогенерации усложняются. Читатель может догадаться, что главным образом я говорю о Си и это несомненно правда. Си - это язык прагматичных, практикующих программистов, который появился из непосредственных нужд упростить процесс кодогенерации. Это не поделка маркетологов или каких-нибудь профессоров, преподавателей-теоретиков. Так вот, язык становится кроссплатформенным в какой-то мере, благодаря абстракции инструкций процессоров и помощи усложненного препроцессора. Кроссплатформенным конечно же на уровне исходников. Не сложно догадаться, что такой подход к эволюции средств разработки завоевывает популярность и даже сейчас Си является основным языком, на котором написано множество ОС (почти все я думаю). И Си незаменим, где-то слышал фразу, что Си основа интеропа между другими языками и нельзя не сказать, что это так, по крайней мере пока ОС написаны на Си. Но далее в истории что-то случилось и всё пошло не так.
Звездец.
Пожалуй в конце 80ых и в начале 90ых, такое устройство как компьютер начинает получать известность и массовость. В сущность вливаются реклама и деньги. Всё подвергается жуткой коммерциализации и прочим прелестям от лукавого. Одним словом - неприятно. Но это не самое страшное, появляются некоторые чёрные концепции типа ООП, АОП, которые намеренно отступают от главной сути языков программирования и дают какие-то свои ничем не подкрепленные понятия. Теперь язык программирования не является средством кодогенерации, а становится вдруг средством выражения идей человека в первую очередь, хотя я считаю это в корне не верно. Нельзя избавится от того факта, что программирование это процесс ориентированный на железо. Т.е. Hardware-oriented programming. В ООП появляются какие-то непонятно чем навеенные понятия: наследование, класс, инкапсуляция, объект, полиморфизм. И они якобы имеют какие-то отношение к реальности человеческой или машинной. Иными словами настал звездец. А теперь я предлагаю вам немного передохнуть, очистить голову от всяких бранных мыслей и продолжить чтение. Далее я раскрою тему того, что я имею ввиду.
Что не так с нынешним путем. Или его отсутствие ;-)
Стоит заметить, что под ООП я имею ввиду все эти новомодные фичи с наследованием, инкапсуляцией, абстракцией и полиморфизмом. Ими в полной мере или частично страдают такие языки как Java, C#, C++, Python, D, Ruby, PHP, Delphi (вероятней всего ваш основной язык программирования в этом списке). А теперь давайте по порядку посмотрим, что же из себя представляют эти фичи.
Абстракцию можно даже не рассматривать, т.к. программирование это такая история про абстракцию. Нет программирования без абстракции. И почему её приписывают к ООП?
Наследование - все помнят типичный пример Shape, Rectangle, Circle. Идея в том, что какие-то структуры данных строятся на основе уже существующих. Каждая Shape имеет площадь, но скажем Rectangle имеет в дополнение к этому такой параметр как отношение сторон, а Circle имеет радиус. Я считаю, что наследование хоть и имеет какую-то логику, но реальная её польза ощутима при использовании понятия полиморфизм. Иначе же это простая композиция данных и/или алгоритмов, т.е. кода. Вообще я бы сказал, что наследование это и есть способ реализации идеи полиморфизма. Помнится в злой науке невнятные понятия частое явление. Есть о чём задуматься? Так, обратно к теме. Пусть наследование и полиморфизм представляют один набор тесно взаимосвязанных фич, на практике же всё это представлено в виде неявного использования таблицы виртуальных функций/методов (vtbl). Т.е. такой простой прием кодогенерации встраивают в язык по какой-то причине и позволяют работать с ним на уровне синтаксиса. Оно и понятно, никому не охото писать myobject->vtbl[METHOD_ID](args). Но фактический в такой фиче нет ничего нового и по сути своей она ничем не отличается от простого использования указателей на функции. Возможно вид несколько иной, пусть и более удобный. Но однозначно фича не стоит того, чтобы о ней писали трактаты и на площадях выкрикивали: "Everything is an object!".
Инкапсуляция - что-то там про сокрытие данных и выставление на показ только интерфейса для пользователя-дебила. Ну хорошо, пусть это средство самоконтроля себя от своих же ошибок. Также можно отнести к средству какого-никакого дизайна/архетектуры, но сомнительно. #define private public - это реальность, а не сказка. Понятно дело, что никакого кода не генерирует. В С++ если использовать класс на прямую, не по указателю, то компилятору обязательно нужно знать все детали его реализации, для определения размера. Так что смысла в кодогенерации нет никакого. Получаем бесплатную порцию жучков мозгоедов.
Про полиморфизм уже было сказано. Подведу я лучше итог по ООП. Однозначно ООП это инструмент разработки дизайна/архитектуры программ и к языку вообще никакого отношения не имеет. Тут правы те, кто знают, что нельзя называть язык объектно-ориентированным, язык может поддерживать средства для работы с объектно-ориентированным дизайном, но не факт, что они вообще нужны.
Куда идти?
Теперь я бы хотел ещё остановится на других элементах языка С++ в частности, которые имеют несомненно большую пользу, чем ООП средства. А также озвучу некоторые случайные идеи.
Более строгая система типов - помимо более строгого контроля ошибок программистов-идиотов, скорее всего несет смысл в виде возможности генерировать более оптимально код. Непосредственно фича кодогенерации, как и система типов вообще.
Шаблоны - неосознанная попытка заменить препроцессор на типизированный препроцессор с возможностями метапрограммирования. Шаблоны сильно полезны в области generic программирования. Непосредственный инструмент кодогенерации. Более совершенная система подобного рода должна рано или поздно заменить устаревший препроцессор. Можно посмотреть в сторону языка D, где препроцессора уже нет и сам язык выполняет его роль на стадии компиляции. Появились даже AST макросы, позволяющие работать с AST напрямую. Однозначно правильная идея развития кодогенерации как данных так и исполняемого кода.
Перегрузка операторов - средством генерации вряд-ли назовешь, скорее синтаксический сахар, но тем не менее польза есть. Просто удобно. А кто не понимает - пусть пишут на Lisp.
Конструкторы, деструкторы - опять инструмент кодогенерации, причем очень простой. Однозначно имеет определенную пользу, т.к. расширяет возможности структуризации кода путем взаимодействия с понятием scope.
Есть в С++ ещё и плохие штуки. Например сломанный static. В Си изначально keyword static является исключительно инструментом кодогенерации данных. В С++ он приобрел невнятное состояние из-за возможности запускать исполняемый код. Это от части проблема того, что в языке нет нормальных инструментов метапрограммирования, от части вероятно недальнозоркость авторов языка. Вроде бы в С++0x должны появиться какие-то фичи связанные с этим в виде constexpr. Посмотрим.
Теперь о GUI. Не поверите как текущие GUI системы себя инициализируют! Вместо того, чтобы программа напрямую содержала snapshot GUI иерархии виджетов и после загрузки программы память сразу начинала с ним работать, в GUI программах содержится код создания этой самой иерархии, который вызывается ровно один раз и является абсолютно не нужным баластом с точки зрения логики. Всё, что можно делать вне runtime'а, должно делаться именно там. Проблема в том, что в современных языках недостаточно выразительных средств кодогенерации данных. Нельзя описать в разумном виде сложную иерархию объектов и использовать её. Языки имеют средство описания массивов например, но в приличном виде невозможно описать скажем red-black tree. Почему? А потому что мыслим в другом направлении. Я однозначно за развитие таких средств, пусть они и будут в виде техник метапрограммирования. Конечно, ничего не мешает мне написать генератор данных и встроить их в Си код, но такие средства должны уметь взаимодействовать сразу с языком программирования. Подведя итоги. В GUI вообще не должно быть кода инициализации самих GUI виджетов. Конечно всегда есть исключения, которые имеют под собой вполне понятное обоснование. Если программа загружает конфиг и в зависимости от значений полученных оттуда что-то меняет в виджетах, это понятно. Но когда программа при каждом запуске запускает один и тот же код, который генерирует в памяти один и тот же набор данных при любых условиях и более того, этот набор данных меньше или равен по размеру самому кода(!), это лишний код и он требует удаления. Поверьте, такой код не редкость!
Хотелось бы напоследок сказать. Товарищи программисты, не забывайте чем вы занимаетесь, осознавайте свои действия в полной мере, смотрите на вещи открытым широким взором. Ищите связь там, где её на первый взгляд нету. Помните, что весь процесс программирования это лишь способ кодогенерации машинных команд и данных, которые этими командами будут обрабатываться.
понедельник, 24 ноября 2008 г.
Насекомые
Сегодня немного необычный пост... о насекомых. А именно речь пойдет о Cimex lectularius, он же Постельный клоп.
Ложусь я значит вчера спать и на тебе, убил одного прямо на себе, смотрю одеяло - второй, отодвигаю подушку софы - третий, четвертый. Думаю - ну всё, не будет сегодня сна. И правда, прямо на софе откопал их гнездо, поселились видите-ли рядом с едой (т.е. я - еда, почувствуй себя едой, ага). Вообщем ещё раз - сон пришлось отменить и весь сегодняшний день я провел в борьбе с этими тварями. Потому что дело серьезное и твари живучие.
Могут без еды существовать по пол года, самки откладывают в день до 12 яиц. Жрут кровь по ночам, укус не чувствуется, попадает гад не с первого раза куда надо, поэтому оставляет 3-4 точки, чешутся.
Убил больше 40+ тварей руками, обработал комнату дихлофосом (читал, что не сильно помогает, но эффект есть всё же), удалил софу из комнаты. Теперь в комнате пусто и прекрасно. Давно мечтал избавится от старой мебели :)
А ещё режим сна вроде вернул в нормальное русло. Надеюсь завтра всё встанет на свои места.
Мораль: во всём плохом и мерзком, всегда есть что-то хорошее
P.S. Не завидую тем, кто столкнется с клопами.
воскресенье, 23 ноября 2008 г.
Блогонеприязнь
Я столько раз начинал блог, что уже сбился со счету. Не знаю почему я их удаляю каждый раз. Видимо так надо зачем-то кому-то. Но на этот раз, я в самом деле даю публично обещание не удалять его и пусть даже он будет пустовать, как некий nsf.blogspot.com (прямо говорит мне - место занято, кыш), но блог будет жить!