LINUX.ORG.RU

Почему макросы в стиле лиспа не стали популярными?

 


3

7

В лиспе есть чудесное свойство: код и данные выглядят одинаково. Это позволяет очень легко и естественно писать код, генерирующий другой код. Что называется макросом.

Однако в индустрии данный подход применяется нечасто.

К примеру в С используется отдельный язык, генерирующий текст (препроцессор).

В С++ используется отдельный язык на шаблонах для метапрограммирования.

В Scheme тоже изобрели отдельный язык.

Из похожих подходов я видел только D, в котором можно написать функцию, возвращающую текст. Эту функцию можно вызывать во время компиляции и её результат компилятор тоже откомпилирует. Этот подход похож на лисп, хотя и гораздо менее удобен. Но больше нигде я такого не видел.

Если говорить про не-лисповые языки, то естественным кажется ввести официальный API для AST (по сути там ерунда) и разрешить писать функции, возвращающие этот самый AST. Это будет всё же лучше, чем текст и концептуально более похоже на лисп. Но я такого не видел. Разве что в Java есть annotation processor-ы, но и там такой подход это на уровне хаков скорей.

Можно сказать, что достаточно популярен подход, при котором генерируется исходный код перед компиляцией, к примеру из openapi спецификации. Это близко к постановке задачи, но всё же не совсем то.

Мне кажется идея генерировать код тем же языком, которым он пишется, на 100% естественной и удачной. Единственное, что я бы тут дополнил - сохранять весь этот сгенерированный код в хорошо отформатированном виде где-то в каталоге с остальными артефактами сборки и предоставлять простую навигацию, чтобы не нужно было гадать, что же там эти макросы нагенерировали. Но это уже мелочи и вопрос инструментария, а не что-то фундаментальное.

Почему же так не делают? Зачем почти в каждом языке изобретают какие-то особые пути для метапрограммирования?

★★★★
Ответ на: комментарий от Nervous

А криков-то было. Интерпретатор нипричёёооом, эвал-вен убер аллеееес! %)

Алло, они и в CL так вычисляются. Ты не понял сути. Еще раз перечитай сообщение Хики.

Компилятор не дает тебе гарантии как и сколько раз макросы раскроются

lovesan ★★
()
Ответ на: комментарий от lovesan

Компилятор не дает тебе гарантии как и сколько раз макросы раскроются

Ну вот, допустим, ты упоролся и хочешь при раскрытии макроса сходить в сеть, получить жсон со списком сигнатур функций, нагенерировать по нему собственно функций и положить ссылки на них в какую-то структуру данных (оставим за кадром вопрос, что мешает сделать это в рантайме — см. условие №1). Что мешает тебе во время раскрытия этого макроса проверять состояние запроса и наличие данных, не отправлять повторные запросы и возвращать сразу готовую структуру при повторном раскрытии?

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 3)
Ответ на: комментарий от Nervous

питонщики же как-то живут вообще практически без метапрограммирования

Просто питон настолько гибок, что в 99% случаев оно там ненужно. Но если нужно - есть ast, есть декораторы, есть фп и функции как перворанговые объекты - при необходимости можно сделать из питона в этом смысле практически лисп, тока лучше:-)

Я уже приводил тут кучу примеров метапрограммирования на питоне - это генерация плюсового кода, символьная алгебра и т.д. и т.п.

AntonI ★★★★★
()
Ответ на: комментарий от eternal_sorrow

Там можно вставить в произвольное место (в том числе внутри функции) eval(process_ast(b + c)), чтобы функция process_ast на вход приняла выражение plus(identifier("b"), identifier("c")) и вернула произвольный AST, которым бы eval заменился? В том числе объявить новые переменные, использовать другие переменные из области видимости и тд.

vbr ★★★★
() автор топика
Ответ на: комментарий от AntonI

практически лисп, тока лучше:-)

Когда наконец напишешь на петоне функцию unless, которая делает то же, что if, только наоборот — поговорим %)

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

А в чём тут мета? Интерпретатор питона ведь остаётся таким же, как был, никаких новых возможностей в нём не появляется. Никакого нового синтаксиса, никакой новой семантики, ничего.

Похоже, это просто программирование.

Nervous ★★★★★
()
Ответ на: комментарий от Nervous

делает то же, что if, только наоборот

В питоне от рождения есть операция not и скобочки. Ваш К.О.

Некоторые задачи не решают не потому что они неразрешимы, а потому что они идиотские.

Интерпретатор питона ведь остаётся таким же, как был, никаких новых возможностей в нём не появляется. Никакого нового синтаксиса, никакой новой семантики, ничего.

Любая перегрузка операций меняет семантику. Можно поменять настройки так, что алгебраическое выражение будет приезжать в виде ast допускающего замены, конвертацию в другие форматы и пр.

AntonI ★★★★★
()
Ответ на: комментарий от AntonI

Некоторые задачи не решают не потому что они неразрешимы, а потому что они идиотские.

Это ж специально упрощённый пример, который, тем не менее, наглядно показывает, что где петон, а где метапрограммирование.

Nervous ★★★★★
()
Ответ на: комментарий от AntonI

Любая перегрузка операций меняет семантику

Если определение новых функций — это метапрограммирование, то что тогда обычное программирование?

Насколько я понимаю, программирование — это описание вычислительного процесса по заранее определённым (автором интерпретатора) правилам. Определение новых функций автором языка петон предусмотрено.

А метапрограммирование — это возможность изменения и дополнения этих правил (изменение самого интерпретатора). Скажем, управление порядком вычисления аргументов функции. Такой возможности в языке петон нет.

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 1)
Ответ на: комментарий от Nervous

Где петон, а где метапрограммирование.

Питон дает возможность писать самомодифицирующийся код через интроспекцию, а не гомоиконность. Это просто разные методы обеспечения этого самого метапрограммирования. Они не равны и один метод более мета- чем другой. Но и то и другое это метапрограммирование.

Гомоиконных языков меньше десятка и половина из них - диалекты/дети лиспа. Остальные используют интроспекцию. Поэтому сводить метапрограммирование только к гомоиконности имхо - неверно.

Obezyan
()
Ответ на: комментарий от Nervous

А метапрограммирование — это возможность изменения и дополнения этих правил.

Ну необязательно, генераторы кода это тоже метапрограммирование. Как и самоизменяющийся код в процессе выполнения. При этом никакого переопределения порядка вычисления аргументов функции там нет.

Obezyan
()
Ответ на: комментарий от Nervous

Плюсовые шаблоны это метапрограммирование?:-)

По умолчанию операция + возвращает сумму, но я могу её перегрузить так, что она будет возвращать разность - это не метапрограммирование?

ИМНО метапрограммирование это любое программирование дающее в итоге программу а не данные.

AntonI ★★★★★
()
Ответ на: комментарий от AntonI

По умолчанию операция + возвращает сумму, но я могу её перегрузить так, что она будет возвращать разность - это не метапрограммирование?

Можно и так.

У меня можно полям и объектам назначить динамически (в т.ч. в run-time) семантику любой сложности.
run-time использует её.

Forum0888
()
Последнее исправление: Forum0888 (всего исправлений: 1)
Ответ на: комментарий от AntonI

Получение ast и его модификация это не гомоиконность?

Нет, это работа через представление внутренних структур языка и работу с этими представлениями, т.е. - интроспекция. И да - это тоже метапрограммиорвание.

Obezyan
()
Последнее исправление: Obezyan (всего исправлений: 1)
Ответ на: комментарий от Obezyan

Мне всегда казалось что интроспекция, это возможность лазить по данным не зная априори о том что это за данные.

В этом смысле гомоиконность это инстроспекция+прямой доступ к ast. У гомоиконности есть правда ещё требования к синстаксису…

@monk - где заканчивается интроспекцмя и начинается гомоиконность?:-)

AntonI ★★★★★
()
Ответ на: комментарий от AntonI

В этом смысле гомоиконность это инстроспекция+прямой доступ к ast.

Нет, гомоиконность лежит на уровень ниже так сказать.

Вообще, гомоиконность Lisp это его побочный эффект из-за «бедности» возможностей во времена его создания. А макросы это буквально суть лиспа, нет макросов нет диалектов в том виде что мы их знаем, ведь всякие setq, defun и даже if - это все макросы.

Obezyan
()
Ответ на: комментарий от Nervous

Ну я например использую

ifch(expr1, cond1, expr2, cond2, epxr3...)

причём эта штука конвертится в разные ЯП в зависимости от, включая LaTeX.

Вполне себе метапрограммирование…

AntonI ★★★★★
()
Ответ на: комментарий от AntonI

Гомоиконность это когда код может быть данными и наоборот без изменения AST, т.е. структура кода и AST имеют одинаковое представление. Буквально, структура кода в этом случае является и структурой данных.

Obezyan
()
Ответ на: комментарий от Obezyan

самомодифицирующийся код через интроспекцию

генераторы кода это тоже метапрограммирование

Неписаное Правило Лора №21: на десятой странице срача можно уже сходить и посмотреть наконец определение.

И действительно, общепринятый смысл термина «метапрограммирование» — это оперирование программами как данными: модификация, генерирование и так далее. Программы высшего порядка. Ну как функции высшего порядка, только программы.

Лисповые макросы, выходит, только частный случай — интерпретатор модифицирует сам себя.

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 1)
Ответ на: комментарий от Nervous

Ну как функции высшего порядка, только программы.

Именно так, лучше и не сказать.

Лисповые макросы, выходит, только частный случай

Да, лисп использует один из самых расово верных методов реализации метапрограммирования, но этот метод все еще один из.

Obezyan
()
Ответ на: комментарий от AntonI

Ну так под это определение и питоньи скачки с ast вполне подходят…

Из меня плохой объяснятор. Вот именно питоньи скачки с AST и определяют что это не гомоиконность, а интроспекция.

Obezyan
()
Ответ на: комментарий от AntonI

@monk - где заканчивается интроспекцмя и начинается гомоиконность?:-)

гомоиконность == структура, написанная в коде, равна структуре, возвращаемой интроспекцией.

monk ★★★★★
()
Ответ на: комментарий от KivApple

Автокомплит и детальная подсветка синтаксиса (а не просто ключевых слов).

В Racket эту проблему решили. DrRacket делает частичное раскрытие макросов + при раскрытии макроса отслеживается источник куска кода.

Но других таких примеров не вспомню.

monk ★★★★★
()
Ответ на: комментарий от monk

Кстати, является ли гомоиконной Scala?

Вот тут мне сложно ответить, она может сувать AST в переменную, но меня все равно клонит к «частичной гомоиконности» хотя такого термина не существует.

и Scheme

Scheme однозначно - да. Как и Clojure.

Obezyan
()
Ответ на: комментарий от Nervous

Куча макросов имеет сайд-эффекты (def макросы по определению сайд-эффектят — модифицируют текущее окружение, это смысл вообще их существования) и никто не делает из этого драмы

Поправочка: есть разница между сайд-эффектами результата раскрытия макроса (код, который макрос возвращает) и сайд-эффектами кода, который генерирует этот код (собственно тело макроса).

def-like макросы — это первый случай и тут по уместности сайд-эффектов ни у кого возражений нет — это обычный кот, которому всё можно. Со вторым немного сложнее — мнения сходятся на том, что там сайд-эффектов надо бы избегать. Но если очень надо… то можно. В крайнем случае спрячем их в eval-when и сделаем вид, что их не было %)

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 1)
Ответ на: комментарий от Nervous

Проблема макросов не в сайд эффектах, вернее это не главная проблема.

Лисп сам по себе очень простой язык с элегантными концепциями, но его полное забивание болта на снижение когнитивной нагрузки делает его для программистов переусложненным. То же желание Лавсана лезть ручонками за .Net либами из лиспа это естественное желание снизить когнитивную нагрузку.

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

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

Obezyan
()
Ответ на: комментарий от Obezyan

меня все равно клонит к «частичной гомоиконности» хотя такого термина не существует.

Скажем гомоиконность есть для алгебраических выражений, но её нет для циклов и условий. Обычное дело…

AntonI ★★★★★
()
Ответ на: комментарий от AntonI

Да, можно сказать что он «местами гомоиконен». Имхо, Scala это попытка скрестить теплое с мягким, сразу из двух парадигм черпают полной ложкой и пытаются выстроить кадавра. Это естественный процесс объединения знаний, но чет пока не задалось также как с объединением ОТО и квантовой механики.

Obezyan
()
Ответ на: комментарий от Nervous

Когда наконец напишешь на петоне функцию unless, которая делает то же, что if, только наоборот — поговорим %)

if – не функция. Вполне можно написать конструкцию

with unless(cond()) as c:
  func1()
  funс2()

в которой функции func1 и func2 выполняются только при ложном результате cond().

Или можно написать макрос

with unless:
  cond() # первое выражение трактуем как условие
  if_false1()
  if_false2()
  ...
  else   # символ else трактуем как разделитель
  if_true1()
  if_true2()
  ...
monk ★★★★★
()
Ответ на: комментарий от AntonI

Я тоже решил пойти к определению. И к списку примеров. И теперь я вообще ничего не понимаю. У них к гомоиконным относятся Java и C# и меньше относится Tcl…

В общем, термин понимает кто как: кто-то приравнивает к наличию «eval», кто-то к программе в виде списка элементов.

monk ★★★★★
()
Ответ на: комментарий от Obezyan

меня все равно клонит к «частичной гомоиконности» хотя такого термина не существует

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

К ней, в частности, относится Common Lisp, потому что в нём есть макросы чтения.

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от monk

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

weakly homoiconic (term invented for this page)

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

Obezyan
()
Ответ на: комментарий от oxo

Так а нахрена тогда Хикки взял S-выражения за основу для языка?

Всё их основное преимущество это в удобстве метапрограммирования, а если считать, что макросы – это баг, а возможность метапрограммирования – это багрепорт о неполноценности языка, то и S-выражения баг в таком случае и в качестве синтаксиса нужно применять что-то более привычное и человекопонятное. А в этом случае лучше писать уж на самой жабе, чем на каком-то франкенштейне поверх JVM будь то кложура или скала или ещё хрен знает что.

Hertz ★★★★★
()
Ответ на: комментарий от lovesan

Народ по привычке всё это думает, что раз с виду (синтаксически) похоже на лисп, значит лисп. Не погружаясь в детали. Хотя какой-нибудь смоллток или фактор и то больше лиспы, чем та же кложура.

Hertz ★★★★★
()
Ответ на: комментарий от Hertz

Народ по привычке всё это думает, что раз с виду (синтаксически) похоже на лисп, значит лисп.

Ящетаю, борщелисп тоже надо как следует проверить на лисповость. А то мало ли чего.

Nervous ★★★★★
()
Ответ на: комментарий от Hertz

Емакс бы ещё с виндой дружил

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

Nervous ★★★★★
()