Pull to refresh

Comments 47

В порядке общего замечания — лучше в качестве примеров приводить какие-нибудь реальные классы с реальными свойствами. Хотя бы на бытовом уровне — class Apple {public Weight: int {get;set;}}; Все эти foo, bar и baz совершенно не воспринимаются.
UFO just landed and posted this here
Нет, он разрабатывается, скоро версия 1 будет.
>но каждый перечисленный вариант реализации идей метапрограммирования обладает своими недостатками, которые не совместимы с идей языка Nemerle.
Извините, но вы не поняли макросы лиспа.

> Во-первых Nemerle компилируемый язык программирования, поэтому отпадают механизмы из Ruby, Tcl и LISP.

Лисп — компилируемый язык. И лисповские макросы, в отличие от существовавших ранее fexpr, созданы именно для компилируемых лиспов.

>Макросы распространяются не в виде исходного текста, а в виде откомпилированных сборок — плагинов к компилятору.
Использование макросов не сложнее использования библиотек.

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

Непонятно, почему в статье проводится дистанцирование макросов немерле от макросов лиспа. На самом деле, немерлевские макросы и лисповые макросы — вещи одинаковые (с той лишь разницей, что в лиспе синтаксис и стадии компиляции более удобные для работы макросов, а в немерле AST сложнее выглядит, т.к. встроенный синтаксис у немерле богаче).
полностью поддерживаю. от себя хочу добавить, что компиляция \ интерпретация языка программирования никак не влияет на то, какие концепции поддерживает язык. Верно и обратно — для любого языка можно написать как интерпретатор, так и компилятор.
Часто языки с динамической типизацией позволяют изменять свой код в runtime, компилируемым языкам это, в основном, не по зубам. Часто они позволяют только достраивать его.
Динамическая типизация и компилируемость — понятия ортогональные (хотя статическая типизация часто облегчает компиляцию). Например, common lisp является компилируемым языком с динамической типизацией.
Лисп, являясь компилируемым языком, позволяет менять код в рантайме. Например, можно переопределять функции, переопределять классы. Кстати, .net тоже позволяет это делать с некоторыми ограничениями (edit and continue в студии же работает).
Это дело техники — нужна достаточна развитая среду выполнения.
Ясно, просто когда я столкнулся с тем, что границы компилируемых и интерпретируемых языков очень размыта (любой скрипт можно запаковать вместе с интерпретатором), провел деление по типизации: статическая => компилируемые языки, динамическая => интерпретируемые.
динамическая типизация и возможность изменять свой код в runtime — понятия ортогональные. например язык C — указатели на фукнцию — это прямой путь менять свой «код» во время исполнения.
Строго говоря, в стандарте языка C запись по указателям на функцию является неопределенным поведением. И не существует переносимого способа изменять код функции.
Нестрого говоря, в качестве примера динамизма можно привести библиотеку ffcall, которая позволяет создавать функции определенного вида («трамплины» для функций обратного вызова) в run-time.
строго говоря «переносимость кода» никак не связана с возможностью изменять код во время исполнения, однако указатели на функцию очевидный путь изменения поведения программы и ее частей во время исполнения.
Предложение «И не существует переносимого способа изменять код функции.» относилось к ANSI C. Вполне можно допустить существование реализаций C, в которых запись в тело функции игнорируется.

С тем, что это очевидный способ менять поведение, я согласен.
Согласен, макросы LISP и Nemerle близки, просто думал, что LISP — интерпретируемый язык.
нет интерпретируемых или компилируемых языков. компиляция или интерпретация — это лишь реализация. для лиспа есть как интерпретаторы, так и компиляторы. точно также и для С есть как компиляторы, так и интерпретаторы.
Если под интерпретируемостью и компилируемостью понимать то, какая модель трансляции языка в машинные коды является основной, предпочтительной, учтенной, эффективной (например, в питоне практически не учтена компиляция в эффективный машинный код), то вполне можно рассматривать интерпретируемые и компилируемые языки.
Либо же можно ввести определения этих понятий на основе величины interpreter overhead.
В любом случае, разделение присутствует.
понятие эффективности — это сложное понятие. Например с точки зрения обучения интерпетаторы всегда более эффективны компиляторов. Или если у Вас код исполняется только однажды (например Вы проводите разовые вычисления) разница «эффективности» компиляции и интерпретации резко стирается. часто интерпретаторы более эффективны компиляторов (раз Вы знаете что такое лисп — наверняка видели кучу примеров).

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

Попробуйте ввести на основе величины interpreter overhead :) Любое конкретное число — ошибка. Разделения не существует.
В стандартах/спецификациях не указывают, как именно надо реализовывать языки, но они пишутся таким образом, чтобы существовала возможность написать эффективный компилятор (никому не нужны замки из воздуха). Иначе почему в java и C# имеем 32-х битный int? Или же пишут не думая или из расчета на интерпретатор, но тогда получается нечто вроде питона, для которого написать эффективную реализацию практически невозможно.

>часто интерпретаторы более эффективны компиляторов

с «иногда» я бы согласился:)

>Или если у Вас код исполняется только однажды (например Вы проводите разовые вычисления) разница «эффективности» компиляции и интерпретации резко стирается

Если этот однократно выполняемый код содержит цикл, то может быть по-всякому.
не нравиться питон — не используйте
не нравиться нативный питоновский инт — используйте ctypes
очень редко когда ограничения обусловленны именно языком, гораздо чаще — самими программистами
просто чаще всего есть куда оптимизировать сам код, чем компилятор или интерпретатор.
Мне питон нравится, я его иногда использую:) Правда, совсем не за быстродействие он нравится. И питоновский неограниченный int мне нравится — типичный пример, когда проектируется от потребностей, а не от железа.
Существуют:
1) чистые компиляторы (преобразующие текст программ в исполняемый код для конкретной архитектуры)
2) чистые интерпретаторы (исполняющие текст программ по одной инструкции за раз, часто в интерактивном режиме)
3) различные промежуточные варианты (компиляция в байткод с последующим исполнением на виртуальной машине, JIT-компиляторы и прочее).

Есть свойства языков, осложняющие компиляцию: динамическая типизация, сильная рефлексия и прочее.

Но тем не менее для любого языка возможны любые варианты реализации (хотя некоторые может быть сложнее сделать). В качестве примера уже приводили LISP, я же могу привести ещё один пример, со Smalltalk-ом, который является очень сложным языком для компиляции (крайне активно использующаяся динамическая типизация, очень сильные рефлективные возможности, возможность на лету изменять стек исполнения и прочие приятные вещи), но тем не менее и для него существуют все варианты реализации, от интерпреторов до компиляторов. При этом самой частой реализацией является компиляция в байткод с исполнением на виртуальной машине (у основных диалектов — с JIT-компиляторами и прочими ускорителями).
>Шаблоны C++ — хорошая технология, но у неё много родовых травм, например, она работает на уровне текста

Шаблоны C++ работают с типами, а не с текстом программы.

> создание своих DSL… не совместимы с идей языка Nemerle

Разве DSL не совместимы с идеями Nemerle? Наоборот, Nemerle подходит для создания DSL.

Создание DSL — это одна из главных задач макросов и метапрограммирования на уровне AST. По сути, изменение синтаксиса языка и включение в него конструкций, позволяющих более удобно решать поставленные задачи — это и есть создание DSL.
Часто DSL ругают, так как он меняет синтаксис, я такой позиции не придерживаюсь, но про него не написал, так как мне кажется, что DSL — маленькая победа по сравнению с возможностью изменять AST программы.
А для чего надом изменять AST программы? Для того, чтобы позволить записывать какие-то вещи более естественным образом. Но ведь это — суть DSL (http://ru.wikipedia.org/wiki/Предметно-ориентированный_язык_программирования: «DSL — язык программирования, специально разработанный для решения определённого круга задач»).
Часто под DSL подразумевается некий внешний, «скриптовый» язык, предметной областью (Domain'ом) которого является предметная область программы, и который слабо или никак не связан с языком программирования, на котором пишется программа. Если пользоваться таким определением, то, действительно, макросы и DSL — разные вещи.
Но если позволить себе включать в Domain у DSL код программы и язык программирования, то макросы как раз являются средством реализации DSL. Например, ваш пример — это DSL для описания сериализуемых классов (очень простой, но, тем не менее, DSL; кругом задач является добавление возможности сериализации к классу), nemerle.org/SQL_macros — DSL для написания частей программы, использующих БД (тут кругом задач являются запрос к БД).
а что, примесей в немерли нету? >_<
и где код этого макроса, который очевидно существует, но никто него не видел?
Примеси в Nemerle есть, но они не решают те задачи, которые решает метапрограммирование.
вот и не надо писать макросы для реализации примесей…
опиши хоть одну задачу, для которой нужны макросы
Сокращение рутинного кода.
это общие слова… пример в студию! B-)
Кусок из реальной программы. Макросы AddTeXASTTests и AddFullMapTests достают из XML файла тестовые данные по ключам CaseA,CaseB,CaseC и создают тесты.

using System;
using NUnit.Framework;
using Core.Macros;
namespace UnitTests.AliveTests
{
  [TestFixture]
  [AddTeXASTTests(CaseA,CaseB)]
  [AddFullMapTests(CaseC)]
  public class ChooseTest : Test { }
}
каккой в итоге-то код получается?
Что-то типа
[Test]
public CaseA()
{
  Assert.Equal("choose(a,b)",TexToAST("a \choose b").ToString());
}
Где TexToAST — метод базового класса Test.
не понял, что это? %-)
под «в итоге получается» я имел ввиду эквивалентный код без применения макросов
1. Поправьте, пожалуйста, «откомпелированных» и «улучщению».

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

Спасибо заранее.
Прежде всего платим сложностью отладки, точнее отладка становиться немного иной — через отладочный вывод и assert'ы — точки остановки уже не поставить. Поэтому метапрограммирование лучше прятать в библиотеки и за простой интерфейс, тогда вероятность, что программист с более низкой квалификацией, чем автор макроса, встретит ошибку будет мала.
А в Nemerle есть возможность посмотреть на AST после преобразования его макросом? В лиспе за это отвечают функции macroexpand-1 (применить раскрытие макросов, но только один раз, не рекурсивно) и macroexpand (раскрыть макросы рекурсивно). macroexpand и macroexpand-1 очень помогают в отладке, особенно когда macroexpand/macroexpand-1 можно применять из IDE.
Кажется да, но я не уверен, когда столкнулся со своей багой посмотрел через Reflector. Активные разработчики и автор интеграции со студией сидят на rsdn.ru можно у них спросить.
Сериализация в C# работает прекрасно и без генерации всяких дополнительных методов, поэтому смысл примера не очень понятен. Если мне нужна какая-то заумная сериализация, я использую DynamicMethod. Приемущество подхода Nemerle тут неочевидно.
Смысл в том, что сериализация является знакомым примером того кода, который не имеет отношения к основному коду программы, но связан с ним. В .NET сериализацию производит runtime и тем самым освобождает от неё код приложения, но если вы используете «заумную сериализацию», то в код приложения попадает код, который напрямую к нему не относиться. Для борьбы с этим кодом удобно использовать метапрограммирование. Дело не в сериализации, а том что бы убрать лишний код из приложения.
АОР тоже убирает лишний код из приложения, но в нем я могу писать в привычном C# и ставить брейкпоинт на сам аспект. Не думаю что в Nemerle можно поставить брейкпоинт прямо в макросе.
Да я и не заставляю писать на Nemerle, сам активно использую C#, просто в одном проекте, который я начал писать на C# столкнулся с тем, что язык Nemerle для него более подходит и здесь делюсь опытом. Да действительно отладка отличается, но иметь ввиду AOP в стиле PostSharp Laos, то брейкпоинты можно будет ставить, так как макрос будет только связывать обычный код и код аспекта.
Очень похоже на использование аннотаций в Java
И это очень хорошо, так как человек, незнакомый с макросами будет думать, что аннотации и спокойно работать с кодом. На самом деле разница большая: аннотации являются маркерами и вся работа с ними идет после компиляции программы на уровне byte-кода или через Reflection. Макросы же работают на уровне компиляции с синтаксическим деревом.

Большой класс задач, которые сейчас решаются через аннотации, можно эффективно решать через макросы, например, AOP.
Речь идет об эффективности кода (производительость) или эффективности разработки (скорости, поддержки и т.п.)?
Тут, мне кажется, и то и то. С одной стороны можно «инлайнить» код, а с другой просто писать меньше и оперировать макросами.
Странно, что ни в комментариях, ни в статье не прозвучало такое слово как «декоратор». А ведь именно о таком инструменте метапрограммирования, как я понял, здесь идет речь.

Во-первых Nemerle компилируемый язык программирования, поэтому отпадают механизмы из Ruby, Tcl и LISP.

Lisp, кстати, тоже компилируемый
Sign up to leave a comment.

Articles