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 ★★★★★
()
Ответ на: комментарий от Hertz

Религиозная вера только в эмакс, каким бы он не был

Воистину M-x.

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

Если строго подходить, то единственным лиспом PicoLisp останется (полная гомоиконичность, код = данные = ячейки с car и cdr).

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

Кстати, в PicoLisp тоже нет макросов «в стиле лиспа».

monk ★★★★★
()
Ответ на: комментарий от 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)
Ответ на: комментарий от Nervous

нет, ну а какие ещё сценарные языки на которых основаны все CGI в Unix/Linux сущетсвуют?)

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

CIDER/clojure-lsp вполне себе подсвечивают макросы

(defmacro mydef (a v)
  `(def ,a ,v))

(mydef varr 123)

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

monk ★★★★★
()

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

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

возможностью скорытия кода

если только… (и именно так 🤭)

aol ★★★★★
()
Ответ на: комментарий от 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

код = данные = ячейки с car и cdr

На IBM ELECTRONIC DATA-PROCESSING MACHINE TYPE 704

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

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

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

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

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

Немерля сто лет назад была нинужно, на которое слегка теребонькали лишь на RSDN (ЕМНИП, была/есть такая площадка?). Оно еще живое, что ли??

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

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

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

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

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

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

Это какое?

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 ★★★★★
()

я кстати согласен что кложа не лисп, но разумеется по другой причине (а не по той шизе что лавсан транслирует): потому что кложа используется в проде

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

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

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

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

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

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

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

как луддиты извращаются только ради того, чтобы одна функция работала и с int, и с float

Это не луддиты, а идиоты

slew
()
Ответ на: комментарий от 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 ★★★
()
Ответ на: комментарий от alysnix

Я не пишу библиотеки на Си++. Поэтому шаблоны не пишу, а использую.

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

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

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

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

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

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

slew
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.