Комментарии 47
В порядке общего замечания — лучше в качестве примеров приводить какие-нибудь реальные классы с реальными свойствами. Хотя бы на бытовом уровне — class Apple {public Weight: int {get;set;}}; Все эти foo, bar и baz совершенно не воспринимаются.
а немерле разве на забросили разрабатывать?
>но каждый перечисленный вариант реализации идей метапрограммирования обладает своими недостатками, которые не совместимы с идей языка Nemerle.
Извините, но вы не поняли макросы лиспа.
> Во-первых Nemerle компилируемый язык программирования, поэтому отпадают механизмы из Ruby, Tcl и LISP.
Лисп — компилируемый язык. И лисповские макросы, в отличие от существовавших ранее fexpr, созданы именно для компилируемых лиспов.
>Макросы распространяются не в виде исходного текста, а в виде откомпилированных сборок — плагинов к компилятору.
Использование макросов не сложнее использования библиотек.
Лисповские макросы распространяются в составе библиотек, которые могут распространяться как в виде исходного кода (который будет скомпилирован), так и в бинарном виде. Плюс есть возможность определять и использовать макрос в пределах одной единицы компиляции, а также определять локальные макросы.
Непонятно, почему в статье проводится дистанцирование макросов немерле от макросов лиспа. На самом деле, немерлевские макросы и лисповые макросы — вещи одинаковые (с той лишь разницей, что в лиспе синтаксис и стадии компиляции более удобные для работы макросов, а в немерле AST сложнее выглядит, т.к. встроенный синтаксис у немерле богаче).
Извините, но вы не поняли макросы лиспа.
> Во-первых Nemerle компилируемый язык программирования, поэтому отпадают механизмы из Ruby, Tcl и LISP.
Лисп — компилируемый язык. И лисповские макросы, в отличие от существовавших ранее fexpr, созданы именно для компилируемых лиспов.
>Макросы распространяются не в виде исходного текста, а в виде откомпилированных сборок — плагинов к компилятору.
Использование макросов не сложнее использования библиотек.
Лисповские макросы распространяются в составе библиотек, которые могут распространяться как в виде исходного кода (который будет скомпилирован), так и в бинарном виде. Плюс есть возможность определять и использовать макрос в пределах одной единицы компиляции, а также определять локальные макросы.
Непонятно, почему в статье проводится дистанцирование макросов немерле от макросов лиспа. На самом деле, немерлевские макросы и лисповые макросы — вещи одинаковые (с той лишь разницей, что в лиспе синтаксис и стадии компиляции более удобные для работы макросов, а в немерле AST сложнее выглядит, т.к. встроенный синтаксис у немерле богаче).
полностью поддерживаю. от себя хочу добавить, что компиляция \ интерпретация языка программирования никак не влияет на то, какие концепции поддерживает язык. Верно и обратно — для любого языка можно написать как интерпретатор, так и компилятор.
Часто языки с динамической типизацией позволяют изменять свой код в runtime, компилируемым языкам это, в основном, не по зубам. Часто они позволяют только достраивать его.
Динамическая типизация и компилируемость — понятия ортогональные (хотя статическая типизация часто облегчает компиляцию). Например, common lisp является компилируемым языком с динамической типизацией.
Лисп, являясь компилируемым языком, позволяет менять код в рантайме. Например, можно переопределять функции, переопределять классы. Кстати, .net тоже позволяет это делать с некоторыми ограничениями (edit and continue в студии же работает).
Это дело техники — нужна достаточна развитая среду выполнения.
Лисп, являясь компилируемым языком, позволяет менять код в рантайме. Например, можно переопределять функции, переопределять классы. Кстати, .net тоже позволяет это делать с некоторыми ограничениями (edit and continue в студии же работает).
Это дело техники — нужна достаточна развитая среду выполнения.
динамическая типизация и возможность изменять свой код в runtime — понятия ортогональные. например язык C — указатели на фукнцию — это прямой путь менять свой «код» во время исполнения.
Строго говоря, в стандарте языка C запись по указателям на функцию является неопределенным поведением. И не существует переносимого способа изменять код функции.
Нестрого говоря, в качестве примера динамизма можно привести библиотеку ffcall, которая позволяет создавать функции определенного вида («трамплины» для функций обратного вызова) в run-time.
Нестрого говоря, в качестве примера динамизма можно привести библиотеку ffcall, которая позволяет создавать функции определенного вида («трамплины» для функций обратного вызова) в run-time.
строго говоря «переносимость кода» никак не связана с возможностью изменять код во время исполнения, однако указатели на функцию очевидный путь изменения поведения программы и ее частей во время исполнения.
Согласен, макросы LISP и Nemerle близки, просто думал, что LISP — интерпретируемый язык.
нет интерпретируемых или компилируемых языков. компиляция или интерпретация — это лишь реализация. для лиспа есть как интерпретаторы, так и компиляторы. точно также и для С есть как компиляторы, так и интерпретаторы.
Если под интерпретируемостью и компилируемостью понимать то, какая модель трансляции языка в машинные коды является основной, предпочтительной, учтенной, эффективной (например, в питоне практически не учтена компиляция в эффективный машинный код), то вполне можно рассматривать интерпретируемые и компилируемые языки.
Либо же можно ввести определения этих понятий на основе величины interpreter overhead.
В любом случае, разделение присутствует.
Либо же можно ввести определения этих понятий на основе величины interpreter overhead.
В любом случае, разделение присутствует.
понятие эффективности — это сложное понятие. Например с точки зрения обучения интерпетаторы всегда более эффективны компиляторов. Или если у Вас код исполняется только однажды (например Вы проводите разовые вычисления) разница «эффективности» компиляции и интерпретации резко стирается. часто интерпретаторы более эффективны компиляторов (раз Вы знаете что такое лисп — наверняка видели кучу примеров).
Часто ли в стандартах (языков программирования) Вы встречаете указания на то, как должен быть реализован язык (в виде интерпретатора или компилятора)? лично я не часто. языки не разрабатываются из расчета того, как код на этих языках будет транслироваться в машинные коды.
Попробуйте ввести на основе величины interpreter overhead :) Любое конкретное число — ошибка. Разделения не существует.
Часто ли в стандартах (языков программирования) Вы встречаете указания на то, как должен быть реализован язык (в виде интерпретатора или компилятора)? лично я не часто. языки не разрабатываются из расчета того, как код на этих языках будет транслироваться в машинные коды.
Попробуйте ввести на основе величины interpreter overhead :) Любое конкретное число — ошибка. Разделения не существует.
В стандартах/спецификациях не указывают, как именно надо реализовывать языки, но они пишутся таким образом, чтобы существовала возможность написать эффективный компилятор (никому не нужны замки из воздуха). Иначе почему в java и C# имеем 32-х битный int? Или же пишут не думая или из расчета на интерпретатор, но тогда получается нечто вроде питона, для которого написать эффективную реализацию практически невозможно.
>часто интерпретаторы более эффективны компиляторов
с «иногда» я бы согласился:)
>Или если у Вас код исполняется только однажды (например Вы проводите разовые вычисления) разница «эффективности» компиляции и интерпретации резко стирается
Если этот однократно выполняемый код содержит цикл, то может быть по-всякому.
>часто интерпретаторы более эффективны компиляторов
с «иногда» я бы согласился:)
>Или если у Вас код исполняется только однажды (например Вы проводите разовые вычисления) разница «эффективности» компиляции и интерпретации резко стирается
Если этот однократно выполняемый код содержит цикл, то может быть по-всякому.
не нравиться питон — не используйте
не нравиться нативный питоновский инт — используйте ctypes
очень редко когда ограничения обусловленны именно языком, гораздо чаще — самими программистами
просто чаще всего есть куда оптимизировать сам код, чем компилятор или интерпретатор.
не нравиться нативный питоновский инт — используйте ctypes
очень редко когда ограничения обусловленны именно языком, гораздо чаще — самими программистами
просто чаще всего есть куда оптимизировать сам код, чем компилятор или интерпретатор.
Существуют:
1) чистые компиляторы (преобразующие текст программ в исполняемый код для конкретной архитектуры)
2) чистые интерпретаторы (исполняющие текст программ по одной инструкции за раз, часто в интерактивном режиме)
3) различные промежуточные варианты (компиляция в байткод с последующим исполнением на виртуальной машине, JIT-компиляторы и прочее).
Есть свойства языков, осложняющие компиляцию: динамическая типизация, сильная рефлексия и прочее.
Но тем не менее для любого языка возможны любые варианты реализации (хотя некоторые может быть сложнее сделать). В качестве примера уже приводили LISP, я же могу привести ещё один пример, со Smalltalk-ом, который является очень сложным языком для компиляции (крайне активно использующаяся динамическая типизация, очень сильные рефлективные возможности, возможность на лету изменять стек исполнения и прочие приятные вещи), но тем не менее и для него существуют все варианты реализации, от интерпреторов до компиляторов. При этом самой частой реализацией является компиляция в байткод с исполнением на виртуальной машине (у основных диалектов — с JIT-компиляторами и прочими ускорителями).
1) чистые компиляторы (преобразующие текст программ в исполняемый код для конкретной архитектуры)
2) чистые интерпретаторы (исполняющие текст программ по одной инструкции за раз, часто в интерактивном режиме)
3) различные промежуточные варианты (компиляция в байткод с последующим исполнением на виртуальной машине, JIT-компиляторы и прочее).
Есть свойства языков, осложняющие компиляцию: динамическая типизация, сильная рефлексия и прочее.
Но тем не менее для любого языка возможны любые варианты реализации (хотя некоторые может быть сложнее сделать). В качестве примера уже приводили LISP, я же могу привести ещё один пример, со Smalltalk-ом, который является очень сложным языком для компиляции (крайне активно использующаяся динамическая типизация, очень сильные рефлективные возможности, возможность на лету изменять стек исполнения и прочие приятные вещи), но тем не менее и для него существуют все варианты реализации, от интерпреторов до компиляторов. При этом самой частой реализацией является компиляция в байткод с исполнением на виртуальной машине (у основных диалектов — с JIT-компиляторами и прочими ускорителями).
>Шаблоны C++ — хорошая технология, но у неё много родовых травм, например, она работает на уровне текста
Шаблоны C++ работают с типами, а не с текстом программы.
> создание своих DSL… не совместимы с идей языка Nemerle
Разве DSL не совместимы с идеями Nemerle? Наоборот, Nemerle подходит для создания DSL.
Создание DSL — это одна из главных задач макросов и метапрограммирования на уровне AST. По сути, изменение синтаксиса языка и включение в него конструкций, позволяющих более удобно решать поставленные задачи — это и есть создание DSL.
Шаблоны 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 для написания частей программы, использующих БД (тут кругом задач являются запрос к БД).
Часто под 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 { } }
1. Поправьте, пожалуйста, «откомпелированных» и «улучщению».
2. Если возможно, то для полноты картины было бы неплохо узнать об ограничениях, области применимости и т.д… О том, чем платим за то, что получаем.
Спасибо заранее.
2. Если возможно, то для полноты картины было бы неплохо узнать об ограничениях, области применимости и т.д… О том, чем платим за то, что получаем.
Спасибо заранее.
Прежде всего платим сложностью отладки, точнее отладка становиться немного иной — через отладочный вывод и assert'ы — точки остановки уже не поставить. Поэтому метапрограммирование лучше прятать в библиотеки и за простой интерфейс, тогда вероятность, что программист с более низкой квалификацией, чем автор макроса, встретит ошибку будет мала.
А в Nemerle есть возможность посмотреть на AST после преобразования его макросом? В лиспе за это отвечают функции macroexpand-1 (применить раскрытие макросов, но только один раз, не рекурсивно) и macroexpand (раскрыть макросы рекурсивно). macroexpand и macroexpand-1 очень помогают в отладке, особенно когда macroexpand/macroexpand-1 можно применять из IDE.
Сериализация в C# работает прекрасно и без генерации всяких дополнительных методов, поэтому смысл примера не очень понятен. Если мне нужна какая-то заумная сериализация, я использую
DynamicMethod
. Приемущество подхода Nemerle тут неочевидно.Смысл в том, что сериализация является знакомым примером того кода, который не имеет отношения к основному коду программы, но связан с ним. В .NET сериализацию производит runtime и тем самым освобождает от неё код приложения, но если вы используете «заумную сериализацию», то в код приложения попадает код, который напрямую к нему не относиться. Для борьбы с этим кодом удобно использовать метапрограммирование. Дело не в сериализации, а том что бы убрать лишний код из приложения.
АОР тоже убирает лишний код из приложения, но в нем я могу писать в привычном C# и ставить брейкпоинт на сам аспект. Не думаю что в Nemerle можно поставить брейкпоинт прямо в макросе.
Да я и не заставляю писать на Nemerle, сам активно использую C#, просто в одном проекте, который я начал писать на C# столкнулся с тем, что язык Nemerle для него более подходит и здесь делюсь опытом. Да действительно отладка отличается, но иметь ввиду AOP в стиле PostSharp Laos, то брейкпоинты можно будет ставить, так как макрос будет только связывать обычный код и код аспекта.
Очень похоже на использование аннотаций в Java
И это очень хорошо, так как человек, незнакомый с макросами будет думать, что аннотации и спокойно работать с кодом. На самом деле разница большая: аннотации являются маркерами и вся работа с ними идет после компиляции программы на уровне byte-кода или через Reflection. Макросы же работают на уровне компиляции с синтаксическим деревом.
Большой класс задач, которые сейчас решаются через аннотации, можно эффективно решать через макросы, например, AOP.
Большой класс задач, которые сейчас решаются через аннотации, можно эффективно решать через макросы, например, AOP.
Речь идет об эффективности кода (производительость) или эффективности разработки (скорости, поддержки и т.п.)?
Странно, что ни в комментариях, ни в статье не прозвучало такое слово как «декоратор». А ведь именно о таком инструменте метапрограммирования, как я понял, здесь идет речь.
Lisp, кстати, тоже компилируемый
Во-первых Nemerle компилируемый язык программирования, поэтому отпадают механизмы из Ruby, Tcl и LISP.
Lisp, кстати, тоже компилируемый
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Метапрограммирование