Drive your career as React Developer with Symphony Solutions!
×Закрыть

Введение в многоэтапное метапрограммирование и метаязыковую абстракцию

Перевод Introduction to Staged Metaprogramming and Metalinguistic Abstraction, Vladimir Ivanov, May 2008.

Данная статья — начало серии статей о метапрограммировании (metaprogramming). Статья представляет вашему вниманию две техники: многоэтапное метапрограммирование (staged metaprogramming) и метаязыковую абстракцию (metalinguistic abstraction). В статье обсуждаются мифы о метапрограммировании, раскрываются некоторые принципы метаязыковой абстракции и многоэтапного метапрограммирования.

1. Что такое метапрограммирование?

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

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

Компилятор или интерпретатор вашего любимого языка — ярчайший пример метапрограммирования, которое действительно повышает вашу продуктивность. Более того, я полагаю, что метапрограммирование — единственный известный путь значительного увеличения продуктивности путём уменьшения сложности[1].

2. Метапрограммирование и индустрия разработки ПО

Почему метапрограммирование сейчас не в «мейнстриме»? Я попытался задать этот вопрос моим коллегам и получил следующие ответы:

  • «Метапрограммирование — это только для ГУРУ».
  • «Для этого необходимо писать парсеры/компиляторы/интепретаторы/трансляторы/и т. п. — а это всё очень сложно».
  • «Программы, генерирующие другие программы, очень тяжело понимать и отлаживать».
  • «Метапрограммирование — большинство людей просто не знают что это такое... Это и причина и следствие одновременно».
  • «Множесто людей думают, что метапрограммирование означает использование шаблонов C++».
  • «Очень сложно найти людей, умеющих это делать. Куда более практичнее нанять сотню индийцев и решать задачу традиционным путём».
  • «Метапрограммирование пригодно исключительно для больших проектов/систем/проблем».
  • «Это же глупо (и ужасно) создавать и учить сотни языков программирования».
  • «Реклама больших компаний заставляет людей использовать другие методы».
  • «Нехватка инструментов. Нехватка теории».

Некоторые из вышеприведенных утверждений — истинны. Однако некоторые — лишь широко распространённый миф. Одна из целей этой серии статей — систематизировать и представить публике материал, который покажет, что:

  • Метапрограммирование может быть простым. Оно может быть доступным не только для ГУРУ.
  • Метапрограммирование может быть эффективно использовано для предметных областей различного масштаба и задач разной сложности.
  • Метапрограммирование приходит в «мейнстрим» прямо сейчас, вместе с ростом сложности решаемых задач.

3. Метаязыковая абстракция

Метаязыковая абстракция — это процесс решения сложных задач путём создания нового языка или создания словаря с целью лучшего понимания предметной области. Такие языки в настоящее время называют языками предметной области (DSL — Domain Specific Languages). В то время как DSL — термин относительно новый, метаязыковая абстракция — старый и хорошо известный путь решения задач[2].

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

Алгоритм решения задач, с использованием этого подхода, может выглядеть так:

  1. Опишите решение вашей задачи, используя высокоуровневый DSL.
  2. Выберите целевую платформу. Это может быть любая платформа, выбранная вами, или же та, которая требуется напрямую (примеры: другой DSL, машинные коды, язык программирования общего назначения).
  3. Если ваш DSL легко и естественно может быть преобразован в примитивы выбранной платформы — сделайте это (напишите процедуру преобразования). Готово.
  4. Замените исходную задачу другой (более простой) задачей: сделать ваш DSL запускаемым на целевой платформе. Идите на п. 1

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

Какие преимущества даёт принцип метаязыковой абстракции?

  • Программы могут быть более читабельными, их легко сопровождать, если они пишутся с использованием языка близкого к требованиям.
  • Возможность следования правилу DRY (Do Not Repeat Yourself) — не повторяйся!
  • Решения имеют тенденцию быть более готовыми к радикальным и непредсказуемым изменениям требований.

Почему последнее утверждение может быть правдой? Функциональные требования, по большей части, привязаны к высокоуровневым слоям металингвистической абстракции. Они «закодированы» в платформенно-независимой и близкой к предметной области форме. Нефункциональные требования склонны быть привязанными к низким слоям металингвистической абстракции. Даже непредсказуемое требование портирования программы на экзотическую платформу может быть исполнено путём замены низких уровней абстракции, без переписывания или влияния на верхние.

4. Действительно ли метапрограммирование сложно?

Любая простая задача может быть решена сложным путём при использовании любой техники. Метапрограммирование — не исключение. Просто не стоит идти этим путём.

Если вы хотите обречь себя на провал в метапрограммировании, воспользуйтесь следующими советами:

  • Изобретите ещё один язык программирования общего назначения, решая задачу управления кофейным автоматом.
  • Придумайте экзотический синтаксис для ваших языков, который вам потом будет сложно парсить.
  • Выберите инструменты/языки/фреймворки, которые не позволяют легко манипулировать/преобразовывать AST.
  • Выберите инструменты/языки/фреймворки, которые требуют 5+ лет опыта работы, прежде чем программист может начать делать что-либо серьёзное.

Во избежание создания переусложнённых решений, целесообразно следующее:

  • Пусть ваш DSL будет простым, насколько это вообще возможно.
  • Думайте о вашем DSL в терминах его AST и держите его синтаксис как можно ближе к AST. Это поможет вам избежать написания парсеров и отладчиков.
  • Выбирайте простые, легкие в изучении инструменты и языки (или подмножества языков) для того, чтобы обеспечить возможность быстрого присоединения новых людей к проектной команде.
  • Выберите один из «программируемых языков программирования». То есть, расширяемый язык программирования, напрямую поддерживающий гомогенное метапрограммирование (homogeneous metaprogramming[3]).

Должны ли мы минимизировать количество слоёв метаязыковой абстракции с целью уменьшения сложности? Если ваш ответ — «да», рассмотрите следующую аналогию: должны ли мы минимизировать количество функций при использовании функциональной декомпозиции или количество классов при ООП с целью упрощения? Конечно же, ответ — «нет». Мы можем извлечь преимущество из хорошо структурированного (fine-grained) дизайна с тонкими, простыми, хорошо изолированными слоями абстракций. Но, разумеется, сложность поддержки не должна расти экспоненциально с увеличением количества языков. К примеру, при процедурном подходе, языки программирования позволяют легко разделять процедуры на части, когда требуется.

Продолжение следует...

Ссылки:

  1. ^ «Using a hierarchy of Domain Specific Languages in complex software systems design» by V.S. Lugovsky
  2. ^ «Structure and Interpretation of Computer Programs» book aka SICP
  3. ^ A Taxonomy of meta-programming systems
  4. Lambda the Ultimate — The Programming Languages Weblog
LinkedIn

61 комментарий

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

> Оставить комментарий ровно в 0 часов 0 минут 1 января — это круто! Ну это не нак уж сложно когда у тебя в запасе еще 2 часа до нового года:)

Тоже про Lisp & DSL: DSLs in Lisp, an example for an advanced DSL development technique in Lisp.http://lispm.dyndns.org/news? I...Це відповідь на оригінальну статтю Фаулера (там же лінк).

2Vladimir Ivanovспєцом вичікували?:)

Оставить комментарий ровно в 0 часов 0 минут 1 января — это круто!:)

З Новим Роком! Бажаю нових творчіх ідей в Новому Році, та їх успішної реалізації!

> Коротко можна сформулювати таку тенденцію у способі програмування, що обговорюється: зміна > функціональних вимог впливає більше на верхні слої металінгвістичної абстракції, зміна > нефункціональних (архітектурних в т.ч.) більше впливає на нижні.100%

> Не думаю шо це повністю питання нижчого рівня.У загальному випадку, звичайно не повністю.> Тобто на питання «компілятор чи інтерпретатор» впливає > семантика верхнього рівня абстракціїСкажу навіть більше — рішення, які приймаються, чи змінюются на нижчих рівнях можуть привести і до перегляду концепцій верхніх рівнів. Як приклад, підхід до декомпозиції концепцій у верхнерівневому DSL може змінюватись у залежності від архітектурних рішень — і це цілком нормальний процес. Але все ж таки є різниця з класичним програмуванням — рефакторити верхньорівневий код набагато простіше і зручніше.Коротко можна сформулювати таку тенденцію у способі програмування, що обговорюється: зміна функціональних вимог впливає більше на верхні слої металінгвістичної абстракції, зміна нефункціональних (архітектурних в т.ч.) більше впливає на нижні.

> А архітектурне рішення — компілятор чи інтерпретатор повідомлень — це рішення, що приймається вже на нижчих > рівнях абстракціі, його навіть можна змінити потім, не ломаючи DSL.Не думаю шо це повністю питання нижчого рівня. Для більшості DSL це правда, але існують багато випадків коли це не так.Наприклад, відомо що мови програмування які реалізують динамічний доступ до scope (тобто динамічний доступ до локальних змінних) мають серйозні проблеми з реалізацією ефективного компілятора.Аналогічно можна стверджувати, що існують DSL для яких як мінімум недоцільно писатикомпілятор. Тобто на питання «компілятор чи інтерпретатор» впливає семантика верхнього рівня абстракції.І навпаки, якшо ми приймемо рішення про використання компілятора, то змінасемантики DSL може спричинити повне переписування компілятора. Тобто рішення нижнього рівня створює обмеження для зміни DSL. Приклад: введення «call/cc» в мову програмування може призвести до переписування компілятораз використанням CPS.

> Було б непогано. ІМХО — це повинні бути тільки якісь> специфічні випадки вроді боротьби за performance...Саме так:) Сервер — мережевий роутер/файрвол. Клієнт — программа, що виконується на remote machine... Задача — по фільтру, заданому клієнтом, відфільтровувати мережевий трафік і відсилати копію клієнту у рантаймі. Клієнт може міняти фільтр адаптивно.Реалізація: клієнт відсилає серверу повідомлення у pcap/BPF-like форматі, що задає умови фільтрації. Наприклад: «src port 1025 and dst port 3389 and dst host 10.0.0.1». Сервер компілює повідомлення в оптимізований машинний код, який спроможний фільтрувати пакети. Цей код багатократно напряму викликається з ядра операційної системи роутера при надхожденні кожного пакету з мережі.Більш узагальнено, там де потребується багатократня обробка одного й того ж повідомлення — його компіляція — це цікаве архітектурне рішення.

> Приклад навести? Було б непогано. ІМХО — це повинні бути тільки якісь специфічні випадкивдроді боротьби за performance або security.

> Компілятор для повідомлень між клієнтом/сервером чи для > конфігураційних файлів звучить стрьомноТут головне, не відкидати цю можливість як неправильне архітектурне рішення апріорі, бо є задачі, де саме таке рішення (компіляція повідомлення від клієнта в машинний код з наступним виконанням останнього) буде оптимальним і зовсім не стрьомним. Приклад навести? > Думаю інтерпретатор/ [де] серіалізатор описаний декларативно за> допомогою іншого DSL буде більш адекватним.Так, це те ж один з варіантів, що є в просторі вибору.Основий фокус, у тому що DSL на якому описується обробка повідомлень та самі формати повідомлень залишається декларативним високорівневим описом рішення незалежним від рішень архітектурного плана.А архітектурне рішення — компілятор чи інтерпретатор повідомлень — це рішення, що приймається вже на нижчих рівнях абстракціі, йоно навіть можна змінити потім, не ломаючи DSL. > До речі, такий підхід значно спростить документування формату повідомлень...+1

Компілятор для повідомлень між клієнтом/сервером чи для конфігураційних файлівзвучить стрьомно. Думаю інтерпретатор/ [де] серіалізатор описаний декларативно за допомогою іншого DSL буде більш адекватним. До речі, такий підхід значно спростить документування формату повідомлень або формату конфігураційного файлу оскільки ми можемо автоматом згенерувати txt, html, tex...з відповідним описом.

> приклад. Нехай навіть такий простий як калькулятор. Краще один раз показати ніж писати тони пояснень. Точно! Приклад з калькулятором можна вже зараз подивитися тут: Simple way of AST transformation using Scheme та Using.NET as a target platform in staged metaprogramming Над більш цікавими прикладами попрацюю вже в новому року.

> Не впевнений, але думаю шо Maxim зачіпив тему обробки DSL в рантаймі>... Тобто мова йде вже не просто про компіляцію в target platform...Компілятор — цікавий приклад програми, яка саме займаєтья обробкою DSL (чи мов загального призначення) в рантаймі.Я просто хотів проакцентувати, що компілятор тоже можна скомпілювати:) І не тільки скомпілювати, а ще й підійти до його розробки шляхом метапрограмування, що не тільки спростить його розробку, а й дозволить його ще й соптимізувати, щоб він був шустрим у рантаймі.

Уточнення: Lisp (або LISP) — це сімейство мов програмування (family of languages).Common Lisp (скорочено CL), та Scheme це найбільш популярні мови з цього сімейства. Мови з сімейства Lisp часто називають діалектами Lisp (Lisp dialect).

Не впевнений, але думаю шо Maxim зачіпив тему обробки DSL в рантаймі.Поправте мене якшо це не так.... Тобто мова йде вже не просто про компіляцію в target platform.Підхід особливо актуальний коли частини системи (наприклад клієнт/сервер) обмінюються повідомленнями в форматі DSL.

ІМХО набагато ефективнішою відповіддю на непорозуміння пов’язані з ЛІСПОМбуде приклад. Нехай навіть такий простий як калькулятор. Краще один раз показатиніж писати тони пояснень. Думаю шо відповіді на поставлені питання очевидні тількидля обмеженої категорії людей.Підозрюю шо автор і збирався продовжити тему в цьому напрямку. До речі, було б непогано підправити термінологію шоб відрізняти LISP — як підхід допроектування мов програмування і Lisp як конкретну мову програмування. Перший включає інші діалекти Lisp, такі як Scheme. Не факт шо використання Lisp є найбільш ефективним для гомогенного програмування.

> То есть перейти к модели хранения данных в динамических языках. Скорость их работы> и потребление памяти хорошо известны. Даже если они не интерпретируются, а исполняются в скомпилированном виде.Здесь даже не важно что Вы несколько недооцениваете современные компиляторы...Попробуйте задать себе вопрос: если Вы считаете, что «модель хранения» неэффективна (причём не важно как оно на самом деле), то что конкретно Вас заставляет перейти к заведомо неэффективной «модели хранения»? И почему бы просто не использовать эффективную? Комуто из известных лисперов (не могу сейчас вспомнить кому) принадлежит фраза: «Вы что, всерьез думаете что у меня есть хоть один список в рантайме??? » Вы можете использовать даже более эффективную модель хранения, чем сейчас, если хотите.Попробую донести мысль ещё раз: если Вы, программируя на Lisp, не используете Lisp как рантайм платформу — то у Вас и нет проблем производительности рантайм Lisp платформы. В качестве утрированного примера, я могу программировать мою числодробильную задачу на Scheme, выполняющейся в браузере поверх JavaScript, а рантайм у меня будет на настольном суперкомпьютере NVidia Tesla с максимально возможной заточкой под фичи последней (экзотическая платформа приведена как платформа, на которой любая реализация JVM, CLR, C++, CL и Scheme будет заведомо неэффективной).Серьезно, что именно из вопросов производительности не понятно? Готов объяснить подробней, если зададите конкретные вопросы.

> Мне вместо class BinaryExpr надо будет хранить class Property, class Node.Да нет, никто вас не заставляет так делать. Вы, по-моему, перепутали Class-based и Property-based наследование с Динамическими и Статическими языками (можете посмотреть на эту тему кратко здесь: http://lisp-univ-etc.blogspot...). Или вы имеете в виду, что внутренности так реализованы? Может, обычный CLOS действительно так работает (при стандартных оптимизационных настройках), но ничего не помешает вам с помощью мета-объектного протокола получить такую модель, которая вам нужна.Но вопрос, безусловно, в другом. Решать задачу в терминах предметной области значит подумать о BinaryExpr, Property или Node как таковых, не привязываясь к тому, что они являются объектами, функциями или еще какой-то категорией. Объекты — это ведь только один из распространенных шаблонов проектирования. Когда у вас будет готова высокоуровневая модель на мета уровне, тогда и нужно будет заняться выбором способов реализации на более низких уровнях. Возможно, на каком-то из них для этого подойдут объекты, может, замыкания или что-то еще. В этом то и отличие парадигмы метапрограммированием от других подходов.А какую модель метапрограммирования вы предлагаете? > Увы, при создании лиспа решение проблемы с ограниченностью ресурсов компьютера не была приоритетом.Тут вы ошибаетесь. Кстати, у SBCL один из самых оптимизирующих компиляторов (потомок компилятора, разработанного в CMU), и если ваша целевая среда — это Java, — то исполняемый код на Lisp будет быстрее (http://shootout.alioth.debian...).

Это то же самое, что сказать, что компилятор C как таковой понимает только printf, но не понимает my_printf. Вы путаете стандартную библиотеку и язык. И, как верно заметил Владимир, кроме языка есть еще «окружающий его способ мышления».Я не понял этой аналогии. Компилятор С не понимает, что такое printf. Он понимает, что такое «вызвать метод с переменным количеством аргументов». Каковым и является printf.В современных компиляторах С/С++ есть расширение его функциональности, которое анализирует аргументы функции printf и выводит предупреждение, если они не соответствуют строке формата. Эти компиляторы понимают функцию printf, но всё так-же не понимают функцию my_printf.Если написать (не знаю, возможно ли) printf на С++ темплейтах — то компилятор всё равно не будет понимать printf, он будет понимать только темплейты.Чтоб среда программирования понимала что-либо, в ней должен быть код, который работает с этим чем-либо. Анализирует, трансформирует и пр. Этот код должен быть либо встроенным в компилятор/среду, либо быть плагином, либо среда должна быть обучаемой.Стандартная библиотека к пониманию кода компилятором отношения не имеет. Она имеет отношение к умению программиста пользоваться данным языком/средой программирования.Как вам удалось прийти к такой оценке? Мне вместо

class BinaryExpr {  Operator op;  Expr left;  Expr right;}

надо будет хранить

class Property {  Symbol sym;  Node data;}class Node {  Property[] properties;}

То есть перейти к модели хранения данных в динамических языках.Скорость их работы и потребление памяти хорошо известны. Даже если они не интерпретируются, а исполняются в скомпилированном виде.Это значит, начать решать ее в терминах предметной области, не думаю об ограничениях конкретной языковой среды, а затем подводить под это решение определенную базу алгоритмов и структур данныхДело в том, что у меня нет ограничения конкретной языковой среды. У меня есть компилятор, который я могу менять как мне нужно.Зато у меня пока есть ограничения среды исполнения. Если я о них не буду думать — моя программа будет потреблять слишком много ресурсов.Вообще лафа с экспоненциальным увеличением производительности компьютеров уже практически закончилась.Увы, при создании лиспа решение проблемы с ограниченностью ресурсов компьютера не была приоритетом.Эту проблему (и другие) я и пытаюсь решить в SymADE. В котором реализую мета-программирование как таковое, без привязки к конкретному языку (онтологии), синтаксису, среде исполнения.

2COTOHA: судя по № 2 статьи в рейтинге (Як платять в ІТ. Мінімалка, ПП та інше), большинство оценок там — за обсуждение.Не может быть такого абстрактного “хорошая статья” или “хорошее обсуждение”. Откровенно плохая статья не может вызвать хорошее обсуждение, а хорошая статья в котором получилось не обсуждение, а какая-то только ссора или холивор наверное не так уж хороша. Так что можно считать что звездочки нужно ставят за статью с комментариями вместе. Может быть статья кажется незаконченной потому что это только первая статья серии, и, надеюсь, Владимир Иванов любезно сделает перевод продолжения.А хорошие комментарии врядли останутся незамеченными, кому интересно, подписался на рассылку комментариев по имейлу или RSS. А кому интересно не очень, пусть лучше сюда не приходять и не портят обсуждение своими плохими комментариями.Интересная также практика из хороших комментариев к посту ДОУ делать целый пост или даже интервью, напримерhttp://cor-win.blogspot.com/20...

я просто тащусь читая это обсуждение! реально приятно почитать.2 Сергей Волошиня вот подумал, что кроме рейтинга статей можно бы сделать ещё и рейтинг обсуждений. Т.к. реально это обсуждение лучшее из прочитанного тут за год, надо чтоб про это все знали. Статья, кстати, как и положено незаконченной, не дотягивает до обсуждения.

> Это значит, начать решать ее в терминах предметной области, не думая об ограничениях конкретной языковой среды, >, а затем подводить под это решение определенную базу алгоритмов и структур данных.Очень хорошо сказано!

> Единственное, что понимает лисп как таковой, это (+ a 2), и всё.Это то же самое, что сказать, что компилятор C как таковой понимает только printf, но не понимает my_printf. Вы путаете стандартную библиотеку и язык. И, как верно заметил Владимир, кроме языка есть еще «окружающий его способ мышления».> У меня сейчас код проекта занимает при компиляции 100Mb в RAM и компилится полторы минуты. На лиспе это будет гигабайт и десять минут.Если вы перепишите код дословно, возможно и будет гигабайт, а, может, и 10 мегабайт. Как вам удалось прийти к такой оценке? Но, по-моему, то, о чем говорил Владимир — это предложение совсем не переходить на Lisp, а посмотреть на задачу с точки зрения метаязыковой абстракции, которая не обязательно должна опираться на Lisp (просто это язык, который лучше всего поддерживает такой подход). Это значит, начать решать ее в терминах предметной области, не думаю об ограничениях конкретной языковой среды, а затем подводить под это решение определенную базу алгоритмов и структур данных. (Это можно делать и на Java, просто не так безболезненно, поскольку при создании языка такая возможность не была приоритетом).

Я бы сказал, что наоборот. Как раз очень многие увлекаются пониманием Lisp’а в таком аспекте, отсюда и такое множество Lisp-диалектов и философских размышлений, им порожденных. Не говорю, что это плохо. Отнюдь. Это значит, что Lisp дает возможность проявиться творчеству. К примеру, Gregory Chaitin (http://cs.umaine.edu/~chaitin/) с помощью Lisp’а вообще взялся исследовать границы математики...Но это не значит, что Lisp как язык (или группа языков, каждый со своими особенностями) не имеет ценности. И я в своей недавней статье здесь пытался показать некоторые практические его преимущества. К сожалению, в других языках попытка программировать так, как считаешь нужным, часто будет попыткой идти против течения, преодолевать ограничения, которые показались разумными их создателям или, что намного хуже, появились сами собой. В большинстве случаев этого не замечают, потому что, во-первых, привыкли к определенному способу мышления и шаблонам решений (не говорю, что они плохие или не эффективные, но нельзя объять необъятное), а, во-вторых, потому что постоянно появляется что-то новое, на осознание чего уходят те силы, которые иначе пошли бы на размышления о более эфективных и простых способах решения конретных задач. Хотя, сейчас наблюдаются определенные изменения (то, что Ola Bini, на которого я ссылался выше, и другие называют: language proliferation). Это в отличие от Lisp-среды, где просто стараются создать условия для того, чтобы программирование было удобным и эффективным в любой парадигме или вне ее. Для меня большим откровением, например, было прочитать в Practical Common Lisp про то, что нет ничего плохого в использовании глобальных переменных (с учетом того, что язык предоставляет инструменты их правильного использования). В Lisp нет табу, но всегда есть решение (а если его нет, значит, просто, никто еще не озаботился его созданием), которое направляет в нужное русло. А если вы хотите нарушать правила — вам виднее и у вас будет такая возможность.

> Но вы уже будете работать с этим DSL, а не с Lisp-ом как таковым.Ну да, Максим, Вы совершенно правильно все поняли! Один из ключевых моментов понимания Lisp-а заключается в том что, Вы пишете Ваши программы не на Lisp! А вместо этого, в заточенной под лично Вас (или Вашу команду) среде разработки, на тех уровнях абстракции на которых хотите, не упираясь в ограничения «мейнстрим» инструментов. Некоторые идут снизу-вверх, просто расширяя Lisp нужными возможностями что очень удобно, например, при использовании Lisp в качестве исполняемой плаформы. Некоторые — сверху-вниз, выстраивая иерархию DSL (семантических понятий, если угодно), что открывает ряд других интересных возможностей...> На лиспе это будет гигабайт и десять минут (при использовании компилятора, а не интерпретатора)...Ну если Вы (вдруг), дейсвительно, буквально воспринимаете что писать на Lisp’е компиляторы — это только лишь обрабатывать все ваши гигабайты сырсов car’ами и cdr’ами — то конечно оно будет работать медленно. Куда уж...Примените метаязыковую абстракцию для написания компилятора, опишите правила преобразования ваших деревьев декларативно на неком DSL высокого уровня. Скомпилируйте (с использованием staged metaprogramming) эти правила в высокоэффективный рантайм-код (хоть в машинный, если хотите) и наслаждайтесь сверхвысокой производительностью компилятора, если она есть цель (на деле же, Вы сильно ошибаетесь в подсчётах, но это и не суть важно в данном контексте).Кто-то (не Вы) может снова скажет, ну вот, опять DSL, причём же здесь Lisp? Ну так собтсвенно в этом и суть:) >...Будет этот код написан на java или на lisp-е — никакой разницы...О, по поводу «никакой разницы». Может вот это подскажет идею: я заметил, что многие люди с богатым опытом программирования на N языках, знакомясь с Lisp, очень быстро находят в нём, скажем так, эквивалент тьюринг-машины, понимают КАК на нём слелать любую задачу (перенося опыт с других языков программирования). И на этом останавливаются, считая фазу понимания завершенной. Та же макросистема рассматриваться только лишь как продвинутая возможность расширять язык, подслащивая иногда себе жизнь синтаксическим сахаром. Точка. Это вполне понятно, такой подход хорошо работал для освоения тех N языков Алгол/С дерева (и даже более-менее работал для освоения парадигмы ООП), но для Lisp он не годиться, ибо это не позволяет увидеть парадигму программирования окружающую Lisp, позволяющую программировать на уровне выражения своих мыслей/намерений [да, да, это можно перевести в том числе и как intentional programming:)], хотя термин «метаязыковая абстракция», мне всё же больше по душе, как более конкретный.Итого: «какая разница? ». Если писать на Lisp в стиле Algol (Java, C++, C#, и т.п.) — действительно нет никакой разницы. Хотя нет, есть разница, на Lisp’е неудобнее (из-а примитивного синтаксиса).Что забавно, бОльшая часть кода Lisp встречающегося публично (и даже некоторые книги) — написаны именно такими людьми «понявшими Lisp», как язык, но не как парадигму, со всеми вытекающими последсвиями:) Кстати, Всеволод, какие у Вас наблюдения по этому поводу? Таким образом, «посмотреть на Lisp», это не на язык (язык простой и тупой как пробка — нет там ни секретов, ни сакральных знаний), а на окружающий его способ мышления. Вы можете применять этот способ мышления и не применяя Lisp, как язык:) Но вот на Java, C# и даже на Nemerle (для тех кто не в курсе — C#-подобный язык с поддержкой метапрограммирования) — руки связяны переусложнёнными инструментами с драконовскими ограничениями. С другой стороны, ирония в том, что уловив идею — свой собственный Lisp-опободобный инструмент сделать просто, именно поэтому Lisp’еры часто смеются, вот дескать, этот чувак на полпути пере-изобретения Lisp. Причём, зачастую, упоминая в дискуссиях «Lisp», лисперы имеют в виду не язык, а способ мышления. А использование примитивного синтаксиса (например, S-выражения) — это, скорее, одно из следствий способа мышления, ориентированного на семантику.Что особенно радует, этот cпособ мышления элементарно осваивается новичками, с хорошо развитым логическим мышлением, особенно, если начинать правильно преподносить Computer Science с самого начала (например, как в SICP).Вот... P.S. чую, теперь, после таких агиток, меня тоже причислят к ряду съехавших с ума фанатов-лисперов:)

Народ, я совсем не хочу сказать, что Lisp не плохо подходит для мета-программирования или в нём невозможно сделать то-то и то-то.Я хочу сказать, что для этого вам в Lisp-е надо реализовать свой DSL, и с ним уже работать. Но вы уже будете работать с этим DSL, а не с Lisp-ом как таковым.Можно написать (binary-expr (operator +) (left a) (right 2)), но для Lisp-а это ничего не значит, это просто данные. Значение этим данным будет придано вашим кодом, вашей реализацией DSL-я, а не Lisp-ом. Единственное, что понимает лисп как таковой, это (+ a 2), и всё.Когда я написал, что «смотрел на лисп» — я не имел в виду поиск компилятора. Я имел в виду идеи по реализации мета-программирования.Ну перейду я на lisp с java, и буду хранить дерево кода в виде s-expression — что это изменит? Мне всё равно надо будет написать код, который будет манипулировать этим деревом. Ну сделаю я к декларации аннотацию, по которой моя среда будет делать в редакторе auto-completion, но мне для этого надо всё равно писать код в редакторе, который будет эту аннотацию (конкретно эту) искать и использовать. Будет этот код написан на java или на lisp-е — никакой разницы. У меня нет auto-completion out-of-box в lisp-е.Но может я не правильно понял, что вы имеете в виду под «посмотреть на lisp». Что там ещё можно смотреть, кромеа) какие заложены идеи, иб) использовать его как реализацию языка программирования? Как реализация, он мне точно не подойдёт. У меня сейчас код проекта занимает при компиляции 100Mb в RAM и компилится полторы минуты. На лиспе это будет гигабайт и десять минут (при использовании компилятора, а не интерпретатора).

Чем отличается синтаксис от семантики — это понятно (формальными методами описания и анализа операционной семантики тоже владеем).Не забудте, пожалуйста, рассказать, что Вы, при этом, подразумеваете под семантическим деревом (а то в интернете и литературе есть очень много разных и противоречивых определений).Ну, у меня достаточно нечёткое понимание, чем отличается синтаксис от семантики.Семантика — это значение. Значение может быть (его можно иметь) только для чего-то и кого-то. Скажем, фаза луны может иметь значение для астролога, а для программиста она значения не имеет. Те формальные методы, которые вы упомянули — они придают значение, семантику. И это значение имеет место быть только с точки зрения данного формального метода.А я семантику в SymADE не фиксирую. Соотвественно, фиксированного значения узлы дерева (код программы) не имеют. Это значение появится только когда вы примените к этому коду какой-то алгоритм, который и придаст ему значение. В отличие от, скажем, «семантического web-а».Поэтому под семантическим деревом (графом) я понимаю просто набор данных, удобный для анализа и работы с ним компьютером, очищенный от промежуточной информации.С другой стороны, синтаксис — это прежде всего удобство для передачи данных или их формального представления. То есть тоже, как и семантика — удобство для чего-то. Разница, получается, только в том — для чего удобный. Поэтому я и не могу провести чёткой границы между синтаксисом и семантикой.Лучше я на примерах попытаюсь показать, что я имею в виду.Скажем, в семантическом дереве у нас не нужны (как правило) комментарии, всякого рода import-ы, не нужны даже идентификаторы — мы уже ссылаемся на нужные данные. В нём невозможно нарушение некоего набора ограничений (он типизирован) и так далее.Синтаксическое дерево в SymADE можно получить из семантического с помощью некоего «проектора». Они служат удобству обмена данными или редактирования или просто являются специфическим view семантического дерева.Например, семантическое дерево можно отобразить в набор синтаксических узлов соответсвующих XML-ю. С целью, например, сделать дамп в XML формат. Или с целью поискать в дереве что-то XPath-ом. Или с целью трансформации этого дерева средствами XSLT.Например, семантическое дерево можно отобразить в синтаксическое с целью удобства редактирования или просмотра определённой части информации. Скажем, выражения удобно редактировать как список идентификаторов, констант и операторов. Или аннотации/метаданные (скажем, как в Java или C#) — их может быть удобно отображать в специализированном виде. Скажем, семантически права доступа (private, public, protected, etc) — есть просто аннотация к классу или члену класса. Или throws в java — это тоже аннотация, что такие-то исключительные ситуации могут возникнуть в методе. Но их удобней/принято отображать не в виде @access (PUBLIC) или @throws (IOException). Достаточно сложно написать «отображатель» кода, который семантическое (удобное для компилятора) дерево будет напрямую рисовать в конкретном синтаксисе (java). Удобней и проще сделать проекцию семантического дерева в другое (синтаксическое) и уже его рисовать и редактировать. Так-же этот проектор может автоматически преобразовывать ссылки на декларации в виде простых имён или квалифицированных имён (в зависимости от того, imported это имя или нет) и так далее.Проекции работают в обе стороны. То есть, после того, как мы сделали проекцию нашего семантического дерева в дерево XML (DOM) и изменили данные в XML — автоматически изменения передаются в семантическое дерево. И изменения в семантическом дереве автоматически передаются и меняют дерево DOM. Можно и наоборот, проецировать синтаксическое дерево в семантическое, что и происходит при редактировании синтаксического дерева, когда вы вставляете (paste) кусок синтаксических данных в код. Или когда вы сделали проекцию в XML, сделали дамп в текстовый файл, а потом читаете этот текстовый файл и из полученного DOMа воссоздаёте семантическое дерево.Но вот в таком виде SymADE будет работать с деревом/графом кода.

Мы можем в лиспе a+2 записать как (+ a 2) — тогда это будет исполняемый код. А если для удобства работы DSL-компилятора этот код записать как (binary-expr (operation +) (left a) (right 2)) — то это уже просто данные, это не исполняемый код... Их даже невозможно унаследовать — это или фиксированные списки (массивы) где понятия индексированы, либо уже что-то более сложное — хэш-таблица, объект и так далее, но это не лисповское дерево кода.

Не совсем так. Тут есть простое и сложное возражение.Простое: в Lisp’е есть keyword-параметры, т.е. вы можете написать (binary-expr: operation +: left a: right b) или наоборот (binary-expr: left a: right b: operation +: meta «huh? »). Кроме того, есть родовые функции, если вы хотите решать вопросы наследования, хотя я не совсем уверен, что они тут обязательно должны возникнуть.Сложное: вы же сами написали, что в Lisp’е код — это данные, а данные — код. Поэтому должны понимать, что никакого различия между (+ a 2) и (binary-expr (operation +) (left a) (right 2)) по сути нет. Обработку и того и другого можно выразить с помощью макросов, как и чего-угодно иного. И для вашего (binary-expr (operation +) (left a) (right 2)) можно написать довольно простое (хотя, как по мне, и чрезмерно сложное из-за того, что задача формулируется не вполне оптимально) решение:

(defmacro binary-expr ((type arg) &rest args)  `(perform ',type ',arg ,@(let ((right-ordering (make-list 2)))                                (loop for (pos arg) in args                                   do (case pos                                        (left (setf (first right-ordering) arg))                                        (right (setf (second right-ordering) arg))))                                right-ordering)))(defgeneric perform (what which &rest operands)  (:method ((what (eql 'operation)) (which (eql '+)) &rest operands)    (apply #'+ operands)))CL-USER> (let ((a 2))           (binary-expr (operation +) (left a) (right 2)))4
Собственно говоря, это и будет DSL.В общем, мне кажется, вы никогда не пытались применить Lisp для решения подобных задач на практике, а рассуждаете о нем на основании собственного теоретического и ограниченного представления. Как сказал Владимир выше:

Хотя, для стороннего наблюдателя, очевидно, люди героически сражаются со сложностями, перепрыгивают барьеры, созданные ими же самими. Эта сложность не воспринимается людьми как результат использования не (совсем) адекватных инструментов. Сложность инструментов воспринимается как «естественная сложность метапрограммирования», ограничения инструментов приписываются «естественным ограничениям метапрограммирования».

На самом деле на все ваши вопросы, если поискать, вы найдете ответы в Lisp’е, наверное, потому что пару десятилетий люди трудились в нем именно над тем классом задач, о которых вы пишите.

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

Что по вашему означает уметь творить, если это не «создавать что-то на основании своего понимания (знаний) "? Возможно, вы про спонтанный акт творения под влиянием вдохновения? Ну, стоит хотя бы заметить, что подавляющее большинство программирования к этой категории не относится.По-моему, вам стоит еще раз обдумать то, о чем вы говорите.:) Мне кажется, что вы отнекиваетесь от термина ИИ только из-за его непопулярности сейчас. По сути же ваш рассказ вызывает стойкую ассоциацию именно с ИИ, и в этом нет ничего плохого. Отрицая же это, вы, просто, заганяете себя в искусственные рамки и, скорее всего, вычеркиваете для себя часть накопленного опыта только на том основании, что это были попытки в области ИИ.Ну, а, если позволите, личное мнение на счет «устранения кодировщика». Суть Lisp-подхода (да и, в целом, метапрограммирования) именно в том, чтобы < quote> прогаммист формулировал задачу</quote>, а все остальное делал компьютер. Но, сформулировать задачу не всегда так просто, как сказать «сложи 2 и 2». Когда речь идет о сложных системах из множества компонент, взаимодействующих с другими системами, то сама формулировка задачи — это тысячи строк информации. Да, компьютер может устранить весь низкоуровневый и boilerplate код (и с этим прекрасно справляются макросы) и таким образом уменьшить код программы в десятки или сотни раз. Но сделать его еще меньше — это уже из разряда ИИ со всеми вытекающими последствиями, одним из которых является недетерминированность результата.

> "Ну и остальные прелести — если я добавил новый атрибут, то все индексы поехали, и надо править всю программу. > И загрузить старый код уже нельзя и пр.«ок, Вы совершенно правы! Так давайте не будем (в таких случаях) ориентироваться на индексы, ни кто, в конце концов, не заставляет описывать Ваши решения на DSL-ях с фиксированной позицонной привязкой семантики, а затем героически преодолевать эти проблемы. Деревья с именованными «атрибутами» смотряться в дереве LISP (читайте в S-выражении), также натурально и естественно как и с позиционными. > Но поскольку Лисп-машина интерпретирует запись программы как дерево с узлом «операция» и под-узлами «аргументы», > то единственный способ создать код — это использовать индексированние аргументов.Не воспринимайте пожалуйста, как упрёк, но как мне кажется, Вы всё никак не можете абстрагироваться от модели исполнения LISP, откудаделаете кучу неверных предположений и выводов. Для любого LISPера будет очевидным, что Вы делаете выводы из вашего ограниченного понимания LISP-метапрограммирования.Ну да, в *базовом LISP позиционная передача параметров. Деление Вам не нравиться. Ну да, вместо удобного во многих случаях однородного ( 2 (reverse 3)), приходится записывать (/ 2 3). И что? У вас же в руках мощнейший инструмент метапрограммирования, не так ли? «Пара движений» умелыми руками — опа — и Вы уже вызываете Ваши процедуры с указанием имен аргументов, и добавляете к ним аннотации с мета-данными.Ещё «пара движений» — и у вас аналог compile-time верификации констрейнтов на эти параметры. Это так, всего лишь, иллюстрации, как можно расширять LISP.А ведь можно же ещё подойти к задаче не только путём расширения LISP снизу-вверх, а ещё путём построения решений сверху-вниз, вооружившись метаязыковой абстракцией, собсвтенно, это я и описываю в своей серии статей о staged metaprogramming. Ещё один пример: > При мета-программировании в лиспе вы даёте компьютеру ровно одно понимание> вашего нового семантического понятияПочему ровно одно? Мне действительно интересно, что конкретно мешает Вам сделать два, три, и даже девять с половиной? Я вот без проблем задаю отображения «понятий» верхних уровней в «понятия» более низких уровней (отсюда, собственно, и возникает иерархия DSL, даже DAG, если точнее), и зачастую, по разным причинам задаю именно альтернативные отображения (нет пресловутого ограничения как MPS). Я принимаю явные решения, когда какая из альтернатив будет задействована, а когда это (в частных случаях) можно автоматизировать, описав простыми и понятными декларативными правилами — просто делаю это. При этом, я не создаю «думающую машину», которая будет принимать эти решения за меня — причём причины чисто прагматические — для этого понадобился бы слишком переусложный инструмент, которого пока нет (и, главное, я понятия не имею как его создать), а мне надо метапрограммирование здесь и сейчас для меня и моей команды, посему я выбираю простейшие в использовании и в обучении инструменты. Это, кстати, не убавляет моего интереса к поиску путей создания «думающей машины» — я люблю сложные задачи:) На 100% уверен, что Вы всё-таки не до конца поняли LISP-метапрограммирование. Однозначно стоит таки посмотреть еще раз, более внимателно, на предмет того, что упустили. Да, для многих людей (особенно с большим опытом программирования) этопсихологически сложно: чтобы это сделать, надо хотя бы допустить для себя возможность, что Вы могли что-то важное упустить при первом знакомстве. С другой стороны мотивирующим фактором для Вас лично, может быть то, что как минимум, это даст массу новых идей для того же Symade;) Если будут сомнения или вы увидите какое-нибудь «фатальное ограничение» — не стесняйтесь — задайте уточняющий вопрос, вдруг вы просто не с той стороны посмотрели, и неправильно всё поняли...Многие опытные программисты (не знакомые с LISP), например, ошибочно ставят знак равенства, между метапрограммированием в LISP и его макросами, что сильно мешает пониманию. С другой стороны, этой проблемы не разу не встречал у новичков [с ними, зачастую, проще — их не надо переубеждать, им достаточно объяснить:)].Чем отличается синтаксис от семантики — это понятно (формальными методами описания и анализа операционной семантики тоже владеем). Не забудте, пожалуйста, рассказать, что Вы, при этом, подразумеваете под семантическим деревом (а то в интернете и литературе есть очень много разных и противоречивых определений).

Но возможность трактовать DSL-и внутри системы как LISP-деревья (хотя концептуально они как бы не являются LISP деревьями) позволяет применить гомогенное метапрограммирование внутри системы, чем в разы сократить сложность разработки/поддержки и даже обучения.Не понимаю как, как их можно «трактовать».Понятно, что они — лисповское дерево. А если я программирую на Java — то они объекты Java. И так далее.Но я не могу трактовать объекты Java как код программы на Java.А чтоб трактовать DSL как лисповское дерево — оно и должно быть лисповским деревом, иначе оно просто данные, такие-же, как объекты в Java.Считаете ли вы «кодирование понятий индеском» единственным способом записи DSL в виде S-выражений? Конечно нет.В Lisp-е данные и код имеют один и тот-же формат, они не отличимы. Потому менять код программы мы может точно так-же как менять данные. Это и порождает возможность мета-программирования в Лиспе. Но поскольку Лисп-машина интерпретирует запись программы как дерево с узлом «операция» и под-узлами «аргументы», то единственный способ создать код — это использовать индексированние аргументов. Точно так-же как при вызове методов в C мы пишем foo (x, y, z) — аргументы понимаются по их позиции, индексу, функция не переставляет аргументы как ей удобней (не интерпретирует это как foo (z, x, y)).Мы можем в лиспе a+2 записать как (+ a 2) — тогда это будет исполняемый код. А если для удобства работы DSL-компилятора этот код записать как (binary-expr (operation +) (left a) (right 2)) — то это уже просто данные, это не исполняемый код. Можно их «выполнить» написав какой-нибудь (eval-expr (binary-expr (operation +) (left a) (right 2:), но точно так-же можно «выполнить» и DSL в виде Java-объектов.Фактически, если вы не используете обращение к узлам дерева DSL-я по индексу — то вы уже создали свой DSL язычёк для работы с DSL. Это может даже не рефлексироваться, поскольку создание DSL-ей в лиспе очень легко делается.Какие проблемы возникают? Очень просто. Их даже невозможно унаследовать — это или фиксированные списки (массивы) где понятия индексированы, либо уже что-то более сложное — хэш-таблица, объект и так далее, но это не лисповское дерево кода. Ну и остальные прелести — если я добавил новый атрибут, то все индексы поехали, и надо править всю программу. И загрузить старый код уже нельзя и пр.Что же даёт такую лёгкость в определении новых DSLей в лиспе? Её даёт тот факт, что вы не описываете полную семантику новых понятий. Вы описываете как новое понятие трансформировать в существующие понятия. И всё.Зачем нужно мета-программирование? Чтоб облегчить разработку программ. Но это только один из способов.Как ещё разработку можно облегчить? Есть несколько путей.а) Создать более высокоуровневый язык (DSL — частный случай). Тогда за счёт работы с более высокоуровневыми абстракциями программу писать и сопровождать проще. Компьютер автоматизирует трансляцию с высокоуровневых абстракций в низкоуровневые. б) Облегчить работу программиста (IDE — частный случай). Компьютер выполняет множество рутинных операций (по редактированию, по анализу кода, поиску связей в проекте, компиляцию в бэкграунде, рефакторинг и многое другое), автоматизируя этот аспект создания программы.в) Облегчить проектирование (UML — частный случай) г) Облегчить анализ констрайнтов (статическая типизация — частный случай) И так далее."Окончательным решением" этой задачи (автоматизации программирования) является, очевидно, написание кода самим компьютером. Устранение кодировщика. Человек (программист) формулирует задачу, и в диалоговом режиме её передаёт компьютеру, а тот внутри себя чего-то думает, и выдаёт исполняемую программу.Так вот, чтоб это «окончательное решение» вообще стало возможным — надо, чтоб компьютер умел, мог, имел возможность манипулировать семантическими понятиями. В том числе и создавать и менять их. Компьютер должен «понимать», что значат те или иные действия в программе.Это не искуственный интеллект, это не создание новых знаний. «Понимать» — не значить уметь творить. Впрочем, это отдельный большой разговор.При мета-программировании в лиспе вы даёте компьютеру ровно одно понимание вашего нового семантического понятия — он его может сконвертировать в другие (уже имеющиеся) понятия. Задать такую конвертацию — сравнительно просто. Именно поэтому мета-программирование в лиспе так просто. И вообще оно очень просто (если есть в языке или другом средстве программирования возможность трансформации одних понятий в другие).Но чтоб добиться настоящего понимания компьютером этого нового понятия, чтоб он мог им свободно оперировать, и в конечном итоге — автоматизировать кодировние — надо задать очень много информации об этом новом понятии. Как минимум на порядок, а то и на два порядка больше, чем просто трансформация в другие понятия.Скажем, что умеет «умное» IDE в отношении имеющихся в языке программирования понятий? Оно их умеет отображать, оно умеет автокомплит и прочую помощь в редактировании, оно умеет рефакторинг, оно умеет анализ на потенциальную ошибочность и так далее. Но зато это позволяет облегчить процесс кодирования.А гипотетическая среда разработки, которая сможет сама кодировать программу — должна содержать ещё больше информации, ещё больше «понимания» для каждого узла дерева. Зато она максимально облегчит процесс кодирования.Я думаю, что отчасти процесс описания понятий в MPS вам показался таким переусложнённым именно по той причине, что вы должны задавать намного больше информации об узле, чем это вам необходимо (по сравнению с лиспом). И это так и есть, особенно если вы пишите этот DSL для частного использования. С другой стороны, если этот DSL действительно пользовать много — то вложения в информацию внесённую в MPS со временем окупятся. (другая часть проблемы — отсутствие у нас навыков, и даже документации по работе с их системой).Есть ли способ решить эту проблему? То есть можно ли сделать среду автоматизации программирования, которая не требовала-бы внесения большого описания для временных и частных понятий (как в лиспе — задал трансформацию и всё), и в то-же время позволяла задавать их в полном объёме для широко используемых (как в IDE и даже больше)? Я верю, что есть. И этот способ — поэтапное задание информации о новых понятиях.То есть такая среда должна позволять, например, просто описать новое понятие в виде «имя» и «набор атрибутов», выбрать один из стандартных способов его использования — и на этом всё. Стандартный способ — это либо трансформация в существующие понятия, либо дамп в XML (если мы делаем язык для описания какой-то конфигурации), либо интерпретацию (evaluation) себя как выражения и так далее.Среда должна сама худо-бедно суметь отобразить это понятие, помогать в редактировании (вроде автокомплита), обеспечить реализацию стандартных способов использования (трансформацию, интерпретацию и пр.).А уже потом она должна позволять, по мере надобности, дополнять «понимание» собой этого семантического понятия.Удобное отображение. Рефакторинг. Отношения с понятиями более высокого уровня абстракции. Частные случаи реализации и т.п. и т.д.Особо продвинутая среда программирования сможет в будущем складывать эти описания в некую «базу знаний» и делать из этой базы «выводы». В конечном итоге она станет настолько полной, что сумеет сама писать конкретный код на основании диалога с программистом. Но это ещё очень не скоро, конечно.Про разделение синтаксиса и семантики — попозже напишу.

> Вы работаете с лиспом, генерите дерево лиспа, и из него генерируете ILЧуть-чуть не туда... Это бы означало что я бы создавал для каждой платформы компилятор LISP, что почти то же самое что фиксировать среду выполнения. По такому пути идти можно (и многие так делают), но как по мне, для огромного класса задач — это overhead. Всё равно что микроскопом гвозди забивать. Представьте себе систему, выполняющую серию трансформаций, преобразуя деревья DSL-ей верхнего уровня, в DSL-и более нижнего, и так пока не дойдём до DSL, представляющий подмножество целевой платформы (или платформ). Преобразование последнего в конкретный целевой синтаксис (машинный код, С++) — как правило тривиально и не является узким горлышком в процессе разработки. Но возможность трактовать DSL-и внутри системы как LISP-деревья (хотя концептуально они как бы не являются LISP деревьями) позволяет применить гомогенное метапрограммирование внутри системы, чем в разы сократить сложность разработки/поддержки и даже обучения.> когда в дереве лиспа используются contionuation-ы? Их в IL не запихнёшь, там совсем другая структура.Хоть не по теме, но всё же отвечу, поскольку вопрос интересный — с трансляцией continuations в IL (это если таки придётся дерево LISP в IL таки перегонять) нет абсолютно никаких проблем. Это достигается преобразованием дерева LISP при помощи CPS conversion — и на IL всё раскладывается как по полочкам. Что прикольно, это даже позволяет довольно эффективно транслировать LISP в виртуальные машины, не поддерживающие tail-call optimization.> Узлы в его дереве представлены как списки, и понятия кодируются индексом в этом списке...> Это приводит к куче проблем при работе с новыми семантическими понятиями (у компилятора).Считаете ли вы «кодирование понятий индеском» единственным способом записи DSL в виде S-выражений? Какие проблемы возникают с новыми семантическими понятиями? Можно поподробнее? Действительно интересно...> привели меня сейчас к идее полного разделения семантического и синтаксического деревьев.также очень интересны детали...

Релиз будет когда документация будет написана: (

уууу. звучит очень похоже на «никогда»., а кто-то пишет её?

В таком случае это не гомогенное мета-программирование, как я его понял. Вы работаете с лиспом, генерите дерево лиспа, и из него генерируете IL. А что вы делаете, когда в дереве лиспа используются contionuation-ы? Их в IL не запихнёшь, там совсем другая структура.Что до лиспа в плане tree rewriting. Конечно я смотрел в его сторону. В итоге он мне не подошёл.Он сам по себе — компромис. Узлы в его дереве представлены как списки, и понятия кодируются индексом в этом списке. В (/ a 2) первый элемент кодирует операцию (деления), второй кодирует левый аргумент и третий правый аргумент. Аргументов не может быть больше 3, это ошибка (операция деления не определена). То есть семантическая часть богаче того, что записано в данных. Это приводит к куче проблем при работе с новыми семантическими понятиями (у компилятора). Для него лучше бы представить это как «BinaryExpr (Operator op, Expr left, Expr right) », но подобный многословный синтаксис будет крайне неудобен для программиста. Вот этот компромис между удобством для компилятора и удобством для программиста в лиспе и решён таким образом.А поскольку я изначально делаю IDE с возможностью произвольного отображения дерева, то у меня самой этой проблемы нет. Я могу отображать дерево с любой степенью детализации. Могу и как (/ a 2), могу как BinaryExpr (/, a, 2), могу как /12. И точно так-же я могу отображать код, который занимается трансформацией дерева. Альтернативные варианты (вроде квази-цитирования) мне тоже не нужны.Конечно, такой подход имеет и свои недостатки. Точнее, один — надо отказаться от текстового представления программы. Даже автоматическая генерация парсера и pretty-printera не помогут, поскольку в дереве могут встречаться практически любые узлы. А у не-текстового представления есть громадный недостаток — неудобно редактировать код. Точнее, не то чтоб совсем неудобно. Это больше похоже на работу в старом vi (или совсем старом edt с pdp11), при редактировании вы оперируете не текстом, а словами, фразами, постоянно переключаетесь между разными режимами редактировния. Для редактирования декларативных DSL — это не большая проблема, после небольшой практики оно так-же удобно, как редактирование текста (а теоретически — даже более удобно). Вот с выражениями — проблема. Она и в MPS есть (практически нерешённая). Я нашёл хороший вариант её решения — однозначность преобразования между деревом и списком обеспечивается заданным приоритетом операторов, и можно «на лету» преобразовывать выражения в список и обратно в дерево, для удобства редактирования. Это достаточно хорошо работало у меня, хотя и не без проблем. Эти проблемы, и ещё некоторые, привели меня сейчас к идее полного разделения семантического и синтаксического деревьев. То есть это два отдельных дерева, и синтаксическое генерируется из семантического когда нужно отобразить особо изощрённым образом этот код, или для удобства редактирования и т.п. Такой себе projection из одного дерева в другое, при этом они остаются связанными, и изменения в одном автоматически (или специальным кодом) транслируются в другое дерево. Над чем я сейчас и работаю.Как закончу — так это и будет публичная бета-версия для SymADE. И ещё надо доделать IDE, чтоб можно было полностью его на самом себе разрабатывать.Релиз будет когда документация будет написана: (

2Maxim: Про homogenous, Всеволод, ИМХО, достаточно хорошо объяснил. Надеюсь, теперь, понятно, что среда исполнения не фиксируется в принудительном порядке (иначе это, действительно, было бы фатальным ограничением). Что забавно, в текущих задачах, я практически НЕ использую lisp/scheme как runtime, а вместо этого генерирую код целевой платформы (это может быть другой DSL, язык общего назначения вроде С++, промежуточный код вроде IL, или, даже, машинный код какой-нибудь экзотической платформы). К метапрограммированию я, кстати, пришел совсем не из необходимости расширения конкретного языка, а из-за задач, в которых выразительные способности имеющихся языков — совсем не годятся. Что касается MPS, на нём можно работать и создавать успешные решения (я разве утверждал что нельзя?) Хоть я и не люблю аналогии, к, примеру, ведь знаете как многие делают: кодирую AST своего DSL в XML-дереве и используют XSLT для генерации кода целевой платформы из xml. Порог вхождения высокий, но так тоже можно создавать успешные решения (особенно, когда человек есть под рукой, которому можно задать любые вопросы по XSLT — и он всё объяснит). Этот подход, во многих случаях, даже окупается (!), что убедительно показывает его применимость. Хотя, для стороннего наблюдателя, очевидно, люди героически сражаются со сложностями, перепрыгивают барьеры, созданные ими же самими. Эта сложность не воспринимается людьми как результат использования не (совсем) адекватных инструментов. Сложность инструментов воспринимается как «естественная сложность метапрограммирования», ограничения инструментов приписываются «естественным ограничениям метапрограммирования». Это рождает целый ряд мифов о метапрограммировании, стереотипов, мешающих увидеть простые по сути вещи. Особенно забавно получается, когда люди их не видящие, начинают создавать т.н. «фреймворки для метапрограммирования».> У меня постоянно стоит задача tree rewrite-а. Заменить одни узлы дерева на другие. Иметь возомжность пройти по всем дереву и найти нужные узлы. И так далее.Кстати, именно проблема AST rewrite, а именно: поиски путей делать это дешево, просто и наглядно, в своё время, привели меня к LISP (разумеется, при этом я прошёл определенный путь переизобретения LISP, включающий создание своих языков/компиляторов и чрезмерное фокусирование на синтаксисе вместо семантики, в том числе). Если Вы этого ещё не сделали ранее, почему бы Вам не попробовать серьезно посмотреть в сторону LISP, вдруг Вы откроете что-то новое для себя, позволяющее смотреть на ту же проблему tree rewrite под другим углом?

2 Maxim Kizub: В идеале, гомогенное и гетерогенное программирование не должно противопоставляться. Это больше как 2 стороны одной медали. В рамках то го же Lisp это так и есть. Примером может служить Parenscript, который предназначен для генерации Javascript кода (т.е. явный пример гетерогенности), который является просто еще одной Lisp-библиотекой.Почему в таксономии написано, что только гомогенные системы могут быть многоуровневыми? Я могу это объяснить только с точки зрения практики. Если у вас есть среда, дающая полноценную возможность создавать DSL’и, то не имеет смысла (или во всяком случае, добавляет ненужную сложность) использование нескольких таких сред. Вы можете на всех уровнях системы производить преобразования, не выходя за рамки этой среды (разве что за исключением последнего, в котором может генерироваться код для целевой платформы). Хотя, чисто теоретически, в гетерогенной среде тоже можно реализовывать разные уровни на разных языках. Что-то вроде Fractal Programming: http://olabini.com/blog/2008/0.../. Оправдано ли это? Думаю, что в некоторых случаях (например, когда есть какие-то «legacy» или «enterprise» ограничения) да, но в общем случае, конечно, нет.Ну, а на счет того, что решать за пользователей, что им нужно. Это вы очень правильно заметили. К сожалению, так поступают в 95% случаев при создании языков и программных сред, в том числе и IDE. От этого, конечно, никуда не деться, поскольку все это создается для удовлетворения каких-то конкретных нужд и целей. Но правильный подход тут должен заключаться в том, чтобы всегда предусматривать возможность расширения, причем на уровне базовой системы, а не только как возможность создания надстроек над ней.

Почитал про гомогенность. Я бы так сказал — предвзятая точка зрения. С большим числом необоснованных (и вообще говоря — ложных) утверждений.Only homogeneous systems can be multi-level — с чего бы это? На MPS разве нельзя создать язык, который будет генерировать программу на другом языке, из которого будет генерироваться программа на третьем языке? Only in a homogeneous meta-system can a single type system type both the meta-language and the object-language -, а кто мешает использовать одну и ту-же систему типов для разных языков в MPS? Больше того, гомогенная мета-система вынуждает использовать только одну систему типов, а MPS позволяет использовать разные. (я не очень знаю нынешнее состояние дел в MPS, позволяют ли они это сейчас практически, но теоретически к этому нет никаких препятствий).И так далее.В конечном итоге у гомогенной среды мета-программирования есть фатальный недостаток, который я упомянул в первом ответе. Она привязывает вас к определённой среде исполнения. Будь то Forth, Lisp или что-то подобное. Её нельзя использовать для другой среды исполнения, и это фатально. MPS, SymADE — позволяют отвязать мета-программирование от языка/программы/модели исполнения. Это мета-программирование как таковое. Вы его можете применить к чему угодно, к любому языку.мы говорим о различной расширяемости. Возможность поправить исходники компилятора — это совсем не та расширяемость языка программирования, которая нужна реальным его пользователямУ пользователей разные потребности, кому-то нужна большие возможности, кому-то нет. В одном неплохом языке программирования вполне было достаточно #define-ов. Многие сейчас считают, что и template-ов достаточно. Кто определяет границы возможностей, предоставляемых пользователю? Вы склоняетесь к гомогенному мета-программированию, и тем самым накладываете огромное число ограничений, но вы их не замечаете. Вам для работы вполне достаточно, и вы считаете, что именно это реальным пользователям и надо.В моей работе я сталкиваюсь с двума достаточно различными типами задач. Первая — это расширение языка, на котром пишу код. Дополнительные проверки, генерация кода, более удобный синтаксис и так далее. Эта часть задач отлично решается в гомогенной среде. Собственно, именно с этого я и начинал, и до сих пор считаю, что возможность расширения языка (в том числе и создение embedded DSL) — это более правильный путь решения для бОльшей части проблем решаемых мета-программированием, чем LOP подход. Но сейчас я пишу IDE, и всё больше превалирует другий тип задач, который требует создания отдельного, ни на что не похожего, не имеющего ничего общего с основным языком DSLя. Это чисто декларативные языки, их даже компилировать ни во что не надо, это скорее удобные языки для описания определённой конфигурации. Скажем, тот-же язык для задания отображения и редактирования узлов дерева. Или по работе я сейчас использовал SymADE для создания удобного редактора для километровой конфигурации хранящейся в XML-е. Или я сейчас работаю над bidirectional отображением одного дерева в другое (чтоб разделить семантическое и синтаксическое деревья программы). В них ничего не надо генерировать или компилировать в исполняемый код. Это банальная конфигурация. И для этих задач LOP работает много лучше eDSL.Я так полагаю, что Сергей Дмитриев пришёл к своим идеям LOP и MPS из проблемы написания IntelliJ IDEA. Вы и я пришли к мета-программированию из проблемы кодогенерации и расширения/адаптации языка под конкретную задачу. Но в жизни нужно и то и другое. Поэтому я не считаю возможным решать за пользователей, что им нужно, а что нет.В случае MPS, она, к огромному сожалению, меркнет на фоне недостатков (стоимость разработки и сопровождения самих DSL в переусложнённой среде) — ага, мне тоже их IDE не нравится. Я так и не смог понять как в ней работать. И всё это помноженное на отсутствие внятной документации и возможности покопаться в исходниках, чтоб понять если что не понятно. Но я читал отзывы одного парня, который устроился к ним на работу. Он пишет — да, входить тяжело, но когда рядом сидит специалист и всегда может помочь — то это как-то восполняет недостаток в документации. А когда уже войдёшь и освоишься — то работать в MPS очень легко и просто. И я ему верю — когда ты уже знаком с этим энным числом необходимых (и достаточно простых) DSLей, специально заточенных под решение этих задач — то дальше решать эти задачи уже просто и легко.В случае же метапрограммирования, Вы можете объявить подмножество вашей прежней среды разработки как target платформу. — этого я совсем не понял. Зачем мне выделять подмножество, если мне наоборот надо расширить функциональность? Может я не так объяснил...Вот я пишу компилятор. У меня постоянно стоит задача tree rewrite-а. Заменить одни узлы дерева на другие. Иметь возомжность пройти по всем дереву и найти нужные узлы. И так далее. Понятно, что задачи специфические, и никто базовый язык под них не приспосабливал. Мне хорошо — я пишу на своём языке, компилятор от которого у меня есть. Я взял и добавил нужные синтаксические и семантические понятия, генерирую кучу кода, мета-программированием решаю свои задачи.А как быть людям, у которых тоже большой проект (на java или c# или вообще на С), и которым тоже хочется вставить кодогенерацию, дополнительные проверкии и вообще задействовать все прелести мета-программирования? Вот одна контора (в которой у меня друг работал) настолько замучалась с null pointer exception в яве, что написала свой почти полноценный компилятор, в котором было практически одно расширение — доказательство на уровне языка, что нулевой указатель не попадётся.Вот этим людям — им как быть с мета-программированием? Вот совсем простой пример. Раньше не было в Java enum-ов. Предположим, кому-то они тогда очень понадобились. Задумали они использовать мета-программирование, и определить такое понятие как enum, и зажить счастливо. Не хотите enum-ы, возьмите pattern-matching. У них уже есть десятки мегабайт кода. На java. И если бы у них был этот pattern-matching, размер кода сократился бы в несколько раз, и они бы смогли и дальше над ним работать, а не мучаться багами.

Спасибо за комментарий, Максим! >... Что имеется в виду по гомогенностью мета-программирования тоже загадка.В статье есть ссылка на taxonomy с неплохими определениями, там есть и homogeneous и staged. Если брать во внимание продолжения этой статьи, то и то и другое раскрывается, причём, на вполне конкретных примерах.> Что до нерасширяемости (и как следствие — ограниченности) — берите аналогичное open-source решение и расширяйте до упаду.мы говорим о различной расширяемости. Возможность поправить исходники компилятора — это совсем не та расширяемость языка программирования, которая нужна реальным его пользователям. Точнее не языка, а метаязыка, но если мы говорим о гомогенном метапрограммировании — то это одно и тоже. > Что позволяет MPS, так это иметь DSL сразу интегрированный в IDE... Делать DSL без IDE — это, как правило, не окупается... Хоть мне и не нравиться MPS в целом, это не мешает похвалить его отдельные части также;) IDE для DSL «из коробки», это действительно одна из прекрасных идей заложенных в MPS. В случае MPS, она, к огромному сожалению, меркнет на фоне недостатков (стоимость разработки и сопровождения самих DSL в переусложнённой среде перевесит преимущества наличия IDE с фиксированными возможностями для полученных DSL).> А что вы можете предложить в качестве альтернативы? Для начала, могу предложить посмотреть на метапрограммирование и процесс разработки под другим углом, попытавшись избавиться от призмы стереотипов, порождённых мифами о сверхсложности и неокупаемости метапрограммирования. Почитать, поинтересоваться темой, попробовать. Если у Вас всё хорошо и используемый процесс разработки адекватен задачам, которые перед Вами возникают — этим заниматься просто не нужно. Если интуиция подсказывает необходимость поиска других процессов, или же просто любопытство раздирает — Вам самое время посмотреть в этом направлении.Чем я могу помочь тем, кому интересно? С удовольствием поотвечаю на вопросы (во всяком случае попытаюсь:)). В следующих материалах, покажу применение подхода не на игрушечных проблемах (типа калькулятора), а на более интересных предметных областях.> А что делать тем, у кого проект уже на мегабайты исходников, и им хочется за счёт мета-программирования облегчить себе дальнейшую разработку? > Переписывать всё с нуля на лисп? Не смешно.Отличный вопрос! Правда, если честно, дальнейшая цепочка рассуждений меня несколько удивила (Вы же участвуете в проекте symade, не правда ли? Вы же не могли всерьез подумать, что я призываю переписывать всё с нуля, когда речь идёт о метапрограммировании?) Переписывать всё с нуля, возможно, было бы проблемой #1, если бы, скажем, предлагался переход с С++ на Java... В случае же метапрограммирования, Вы можете объявить подмножество вашей прежней среды разработки как target платформу. Это позволит новые разработки и расширения функционала вести на качественно новом уровне и, в то же время, иметь «бесшовное» соединение с legacy кодом... Инкрементальный подход к рефакторингу может помочь адекватными усилиями перевести наиболее критические части legacy кода на DSL высокого уровня. Ну и потом, речь не идёт о «переписывании программ на LISP», а о «написании программ на DSL».

Тема многоэтапного метапрограммирования не раскрыта. Вообще не сказано, что это за зверь. Поэтому наезды на MPS за отсутствие этой самой многоэтапности выглядят совсем странно. Что имеется в виду по гомогенностью мета-программирования тоже загадка.Что до нерасширяемости (и как следствие — ограниченности) — берите аналогичное open-source решение и расширяйте до упаду.Ну и раз вы знаете как не надо мета-программировать — скажите как надо. На вашем блоге есть только примеры на lisp (scheme?). Вы к этому зовёте? А что делать тем, у кого проект уже на мегабайты исходников, и им хочется за счёт мета-программирования облегчить себе дальнейшую разработку? Переписывать всё с нуля на лисп? Не смешно.Что позволяет MPS, так это иметь DSL сразу интегрированный в IDE. Сам по себе DSL может значительно облегчить работу программиста. Естественно, DSL имеет смысл писать только для больших проектов, для мелких быстрее взять язык общего назначения. Но с другой стороны, IDE тоже значительно облегчает работу программиста, и именно для больших проектов. Делать DSL без IDE — это, как правило, не окупается. Что-то вы получаете и что-то теряете, не говоря уже о затраченной работе и времени.MPS предлагает решение, в котором DSL уже интегрировано в IDE, и вы ничего не теряете в производительности и удобстве работы.А что вы можете предложить в качестве альтернативы?

> Это какое-то отношение имеет к этой вот хрени? > http://www.jetbrains.com/mps/i...Имеет, как один из моих самых любимых примеров о том, как НЕ должен выглядеть фреймворк для метапрограммирования.Авторы MPS очень хорошо идентифицируют проблdемы mainstream programming (см. тут: http://www.jetbrains.com/mps/d...) и смотрят, в принципе, в правильную сторону, однако взгляд на метапрограммирование у них, к сожалению, сильно ограниченный. Основная проблема MPS — это её фиксированный (нерасширяемый), сложный в семантике и синтаксисе, метаязык. Как результат, метапрограммирование в MPS — не гомогенное, что очень сильно усложняет создание DSL, написание и сопровождение кода. О возможности staged metaprogramming даже и речи нет. Для решения простых задач MPS слишком сложна из-за высокого порога вхождения. При решении сложных задач вы сразу упретесь в лимиты фреймворка.Если вы хотите обречь себя на провал в метапрограммировании, воспользуйтесь следующим советом: используйте MPS.Да, bialix, вы правы, это действительно криво звучит по русски:)

наоборот. это та херня имеет к этому отношение:)

Это какое-то отношение имеет к этой вот хрени? http://www.jetbrains.com/mps/i...

Эээ. А можно было не переводить оригинал, а все таки пересказать это человеческим языком? > "Если вы хотите обречь себя на провал в метапрограммировании, воспользуйтесь следующими советами: "По-русски так не говорят, если не хотя во что бы то ни стало сделать понимание текста читателем как можно сложнее и не доступнее. Мой поинт заметен?

Андрей: там был гриф и страус. Ворона из другого мультика.

Пишу в основном на Питоне. С созданием новых языков, парсеров и проч не заморачиваюсь. Не нужно.А встроенные DSL создаю постоянно. Т.е. интенсивно используя декораторы и метаклассы в разы сокращаю объем кода, генерируя его на лету. При этом сохраняю синтаксис Питона -, но создаю свою семантику.Результат мне нравится. Ребята иногда жалуются на то, что всякие IDE перестают работать так, как им хочется. Например, отказываются выполнять автодополнение и кричат о несуществующих именах атрибутов (они появляются только в runtime). С отладчиком бывают сложности — глубокий стек вызовов. Это — личные проблемы сотрудников. Я IDE не люблю и не использую. Зато все отмечают лаконичность — и этот довод все перевешивает. Меньше кода — меньше ошибок. Да и отладчик становится почти не нужным при правильно организованном процессе — и его неиспользование тоже экономит время. Часто подход напоминает фразу из известного мультика — «лучше час потерять, но потом за 5 минут долететь». Если владеешь материалом — можно избежать незавидной участи вороны. Если нет — проще все-таки быть страусом.

Макс: ты знаешь, часто думаю над этим, когда пишу что-то новое в Емаксе, жму Tab и жду, чтобы он сам за меня закончил «фразу». Только название для него должно быть, наверно, dwit (do what I think):))

Сева:, а макрос ’do-what-i-mean у вас уже тоже есть? (dwim);)

ctapbiu.mabp.myopenid.com: Примеров можно привести много, но для того, чтобы многие из них воспринять, нужно быть чутьчуть в теме. В таких случаях для понимания реального выигрыша полезно увидеть дизайн какой-нибудь системы целиком, а не отдельный пример.Но один все же попробую привести: макрос меньше 10 строк, который управляет всеми действиями, связанными с установкой соединения с БД, пулингом соединений, а также использованием одного соединени для нескольких запросов. Естественно, использующий другие более низкоуровневые макросы. Его применение — в сотне или тысяче мест в коде, где выполняются запросы не нужно вообще знать ничего о том, с чем мы работаем: к примеру, с PostgreSQL, или Oracle, или вообще каким-то in-memory хранилищем, через сокет или библиотеку и т.д. Помнить нужно только, что весь такой код нужно обернуть в форму (with-dao...).

2kurtis99Forth — тема. В «just for fun» Торвальдс пишет как упивался фортом (а потом сожалел о безцельно потраченых часах:).Написать свою форт-машину и лисп-машину — некисло мозги настроить.

Слово индус имеет отношение к религии индуизм.

Давноненько так не сміявся. Взагалі-то, це — національність. Чоловік же, який сповідує індуїзм зветься «індуїстом» (див. http://www.slovnyk.net/? swrd=%...).

2ctapbiu.mabp.myopenid.com: Продолжение можно прочитать прямо сейчас на сайте автора: http://xhalt.blogspot.com/

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

2 Denixэто авторский перевод текста, того же автора, так что автор наверняка имел в виду «индусов»: -) 2 kurtis99 не очень-то и похоже.: -)

манипулируют ими как данные...

Как данными.

это позволяет программистом сделать намного больше работы за единицу времени по сравнению с написанием кода вручную

Вполне естественно, что программистом писать код быстрее, чем руками:)

Я могу ошибаться, но по моему это все на Форт (Forth) немного похоже...

Угу, а на Лисп — много похоже:)

Думайте о вашем DSL в терминах его AST и держите его синтаксис как можно ближе к AST.

Я могу ошибаться, но по моему это все на Форт (Forth) немного похоже...

да уж, чтиво на сон грядущий самое оно) давай еще — почитаем.*опечатка в заголовке)

2Denix: исправил на «индийцев».

О...Виталий Луговский. Всех в биореактор! (RU.LINUX circa 96).

Indians — не переводится как индус. Слово индус имеет отношение к религии индуизм.

Подписаться на комментарии