LINUX.ORG.RU

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

 


3

7

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

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

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

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

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

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

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

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

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

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

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

Было бы интересно почитать лонгрид с со сравнительным анализом ортодоксальных лиспов, модерновых, всяких схем и прочих на соответствие православным критериям. Who is who, короче.

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

На эмаксе сижу 9 лет, кложура, кстати, пока изучал, подтолкнула на него с вима уйти. А на венду вернулся с того момента как попал под сокращение и контора отдала рабочий бук, оставил как есть, стоит 11 вантуз и пусть стоит. Возьму потом неттоп, накачу на него гентушку/фунтушку, а пока пусть покрутится в wsl. Стал агностиком и пофигу что в качестве ОС использовать. Религиозная вера только в эмакс, каким бы он не был.

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

У меня в msys2 не работает truecolor и не хочет ставиться el-get, но всё это на удивление прекрасно работает в православном цигвине.

Для запуска, чтобы запускался только эмакс, без окна консоли, накопал такой батник (сам я их ни читать ни писать не умею):

$ sed -E '/^[[:space:]]$/d' emacs-gui.bat
@echo off
Setlocal EnableDelayedExpansion
set CYGDIR=C:\cygwin64
set CYGBINDIR=%CYGDIR%\bin
set PATH=%PATH%;%CYGBINDIR%;%CYGDIR%\usr\local\bin;%CYGDIR%\usr\bin
cd /d %CYGDIR%\home\%USERNAME%
if [%1]==[] goto BLANK
set VAR=
for /f %%i in ('%CYGBINDIR%\cygpath.exe %*') do set VAR=!VAR! %%i
echo %VAR%
start %CYGBINDIR%\mintty.exe -i /bin/emacs.ico -w hide /bin/bash --login -c "/bin/emacsclient --alternate-editor=/bin/emacs %VAR%"
goto DONE
:BLANK
start %CYGBINDIR%\mintty.exe -i /bin/emacs.ico -w hide /bin/bash --login -c "/bin/emacsclient --alternate-editor=/bin/emacs -c"
goto DONE
:DONE
Hertz ★★★★★
()
Ответ на: комментарий от monk

Неа, тем более последний билд - трёхлетней давности, 20200930. Использую цигвиновскую сборку, она устраивает.

Подглючивает, бывает, таким вот: Doing vfork: resource temporarily unavailable

Делаю rebase и еду дальше.

find ~/.emacs.d/eln-cache -name '*.eln' | rebase -O -T -

Попробую такой w/a,

(when (eq system-type 'cygwin)
  (setq no-native-compile t))
Hertz ★★★★★
()
Ответ на: комментарий от monk

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

CIDER/clojure-lsp вполне себе подсвечивают макросы, делают правильные отступы (настраивается), показывают аргументы, документацию и всё такое (не уверен, кто из них за что конкретно отвечает). Удобный макроэкспанд выражений тоже имеется; вроде бы даже отладчик (который edebug-подобный) по макросам ходит.

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

нет, ну а вообще если лиспа — то только Common Lisp 🫣 назовите мне яп с ещё большей абстракцией, возможностью скорытия кода и силой скобок, ну или хотя бы с большой абстракцией) а вообще что такое это ваше метапрограммирование/декларативное программирование помимо modular никто толком не знает 🔥

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

Подсветит varr как переменную?

Да, если сконфигурировать линтер (clj-kondo), который clojure-lsp использует под капотом (можно использовать его отдельно, например, в конвейере CI). Унихвай, понемаешь.

Изначально в этом коде

(ns dev.1brc)

(defmacro mydef [a v]
  `(def ~a ~v))

(mydef varr 123)

varr

все упоминания varr будет подчёркнуты как ошибка по причине clj-kondo [unresolved-symbol]: Unresolved symbol: varr

Если добавить правило в конфиг линтера (на уровне системы, пользователя или проекта) — ошибка пропадёт:

;; my-project/.clj-kondo/config.edn
{:lint-as {dev.1brc/mydef clojure.core/def}}

Кладём конфиг в гит, профит. (clojure-lsp автоматически находит и применяет эти конфиги для зависимостей, в которых они есть.)

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

{:lint-as {dev.1brc/mydef clojure.core/def}}

Вот это я и имел в виду под плохой поддержкой макроса. Вот что этому линтеру писать, если макрос выглядит так:

(deffuns text-tag-table
  (add :void (tag pobject))
  ((text-tag-table-remove . remove) :void (tag pobject))
  (lookup pobject (name :string))
  (:get size :int))

здесь создаются add, text-tag-table-remove, lookup и size.

Определение макроса вот: https://github.com/Kalimehtar/gtk-cffi/blob/master/g-object/defslots.lisp#L133

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

Вот что этому линтеру писать, если

Ну это надо в его документации смотреть. Будет немного посложнее, конечно.

А вообще это даже неплохо, мотивирует соблюдать правило №1 Клуба Писателей Макросов %)

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

Ну это надо в его документации смотреть. Будет немного посложнее, конечно.

А в Racket сразу работает. Причём при наведении на идентификатор подсвечивает именно тот кусок макроса, из которого этот идентификатор получился. Вплоть до подсветки x в выражении (struct pos (x y)) при наведении на pos-x.

В Emacs/SLIME/SBCL в лучшем случае указывает на форму, определившую идентификатор. То есть на size даст ссылку на всю конструкцию deffuns, а строку в ней сам угадывай.

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

deffuns

Смахивает на кложурный defprotocol. В Ълиспе разве нет стандартного способа определить полиморфный интерфейс именно как интерфейс, то есть набор логически связанных функций (а не по одной)? И каждому приходится городить это самому?

`(defmacro ,name

Ммм, макросы, генерирующие макросы. Смакота.

А в три этажа — может завернуть?

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

Смахивает на кложурный defprotocol.

Нет. Это генератор FFI. Он кроме аналога defprotocol ещё и реализацию методов формирует. И объявление FFI функций для вызова из этой реализации.

Вот же тело: https://github.com/Kalimehtar/gtk-cffi/blob/master/g-object/defslots.lisp#L63

Ммм, макросы, генерирующие макросы. Смакота.

Так если надо сделать три однотипных макроса. Предпочитаете копипаст?

А в три этажа — может завернуть?

При необходимости, легко.

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

Копипаст-драйвен-девелопмент — наше всё.

Иногда это лучше всратой многоэтажной макросни, которую никто в здравом уме не понимает %)

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

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

Иногда это лучше всратой многоэтажной макросни, которую никто в здравом уме не понимает %)

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

Что сложного в том же макросе template, который берёт список наборов параметров и для каждого набора вставляет код?

Вместо

(template (name prefix) ((defgtkgetter 'gtk)
                         (defgdkgetter 'gdk)
                         (defgetter (get-prefix)))
  `(defmacro ,name (name res-type class &rest params)
     (expand-deffun ,prefix name res-type class params :get t)))

можно было написать

(defmacro defgtkgetter (name res-type class &rest params)
   (expand-deffun 'gtk name res-type class params :get t))
(defmacro defgdkgetter (name res-type class &rest params)
   (expand-deffun 'gdk name res-type class params :get t))
(defmacro defgetter (name res-type class &rest params)
   (expand-deffun (get-prefix) name res-type class params :get t))

Но в этом случае не очевидно, какие параметры поля осмысленно разные, а какие случайно и ошибка типа

(defmacro defgetter (name res-type class &rest params)
   (expand-deffun (get-prefix) name res-type class params :set t))

легко прошла бы незамеченной.

monk ★★★★★
()

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

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

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

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

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

Ага. Копипаст-драйвен-девелопмент — наше всё.

Можно писать функции. Компилятор заинлайнит (я про компилируемые ЯП, всякая питонятина пусть страдает). Можно шаблоны в C++, они для этого предназначены. Мощнейший метод, если разумно их применять, а не пытаться выпез*нуться перед такой же школотой.

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

я про компилируемые ЯП, всякая питонятина пусть страдает
Можно шаблоны в C++

Проигрываю, как луддиты извращаются только ради того, чтобы одна функция работала и с int, и с float :) «Ради чего, мистер Андерсон?!»(с)

yu-boot ★★★★★
()
Ответ на: комментарий от slew

Можно писать функции.

Можно. Если копируется алгоритм, а не структура данных или однотипные функции.

Можно шаблоны в C++, они для этого предназначены.

Шаблоны могут создать только класс или функцию. Но не могут даже геттер+сеттер автоматически сделать.

Или вот: https://github.com/boostorg/bind/blob/develop/include/boost/bind/bind.hpp#L548 – почти-копипаст на страницу вверх и вниз.

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

почти-копипаст на страницу вверх и вниз.

Вот и хрен с ним. Абсолютно понятно что там. Бумага все стерпит, это вообще не проблема. Но вот хитровывернутые конструкции, которые не только человек не поймет, но и компиляторы зубы обломают, вот это сильно хуже копипасты и вообще зло. Встречал такие, писанные под одну версию gcc, которые при попытке портировать на богомерзкую, ломали зубы msbuild-у. Сыпал ошибками. clang тогдашний вообще крешился на том участке, бедный. Зато не копипаста и занимало экран. Разбирал эти макросы тщательно, символ по символу, превратив в ту саму копипасту на два экрана. Зато стало понятно и любой компилятор радовался.

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

Это нудно писать и нудно читать. Всё равно, что в языке без массивов обрабатывать сотню чисел через переменные a1, a2, a3, …

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

А это как раз проблема сишных макросов. Да и плюсовых шаблонов тоже.

Там, где на лиспе можно написать простую прямолинейную конструкцию, на Си приходится извращаться через многослойную подстановку, а на Си++ использовать магию SFINAE.

И потом при неправильном использовании Racket подсветит место и укажет «ожидался идентификатор, вижу число», а в Си++ будет портянка на несколько мегабайтов про то, что «не сопоставилось с шаблоном».

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

а в Си++ будет портянка на несколько мегабайтов про то, что «не сопоставилось с шаблоном».

Поэтому за шаблоны в цпп погромисту вручается волчий билет и «вон из профессии». Если по хорошему.

У них вообще есть область применения? То есть, чтобы было идиоматично и, если прыгнуть выше, изящнее?

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

Поэтому за шаблоны в цпп погромисту вручается волчий билет и «вон из профессии». Если по хорошему.

Ну попробуй без шаблонов написать хотя бы полный аналог map<string, string>. И чтобы производительность для всех операций была не хуже того, который с шаблонами.

У них вообще есть область применения? То есть, чтобы было идиоматично и, если прыгнуть выше, изящнее?

Библиотечный код. Альтернатива - или копипаст для всех возможных типов или работа с void*.

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

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

а вот какой самый любимый и нужный шаблон на с++ вы написали, чтобы это запомнить на всю жизнь?

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

Поэтому за шаблоны в цпп погромисту вручается волчий билет и «вон из профессии». Если по хорошему.

ну нет. шаблонить надо там, где это дает явные преимущества и где уж больше ничто не спасает.

шаблонить на любой чих - моветон. но и копипейстить(если другого решения нет) - тоже моветон.

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

Поэтому за шаблоны в цпп погромисту вручается волчий билет и «вон из профессии».

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

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

А это как раз проблема сишных макросов. Да и плюсовых шаблонов тоже.

Давай исходить из реальностей современной индустрии софтоклепства. Если так, то мы имеем дело с C, C++, JS, golang, C# и ряд менее распространенных вещей. Тут нет лиспов. Как бы там не пытались доказать обратное, приводя примеры якобы использования, но которые только подтверждают отсутствие представленности лиспов в современной индустрии. Поэтому, говоря про макросы, подразумеваем макросы из вышеперечисленных ЯП. Иначе тема не имеет никакого смысла, кроме как пофапать на никому не нужные расчудесные макросы лиспа.

slew
()