LINUX.ORG.RU

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

 


3

7

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

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

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

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

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

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

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

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

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

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

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

Цивилизация синоним слова специализация.

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

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

Вообще эта гомоиконность бывает?

Бывает в широко известных в узких кругах языках, например Rebol, Red (база не список, а дерево, и скобки квадратные, так что прямо к лиспам не отнесешь). Или Refal, его уже совсем к лиспам не отнесешь, это отдельная парадигма, даже и не функцинальщина, в базе у него цепи Маркова, а не исчисление Черча.

anonymous
()

Автокомплит и детальная подсветка синтаксиса (а не просто ключевых слов). Придётся при каждом изменении исходника гонять все эти макро-функции. Язык то написать можно, а вот с IDE будет тяжело. Сообщения об ошибках компилятора тоже будут менее информативными. Чтобы язык стал популярным в 2023 году, придётся решать эти весьма нетривиальные проблемы.

А так из свеженького можешь посмотреть, например, на Zig. Там как раз можно писать compile-time функции почти с тем же синтаксисом, что и runtime (просто им доступны специальные операторы для манипуляций над данными о типах).

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

Так ведь без макросов ещё хуже выходит. Прицепляют внешнюю хреновину на yaml’е или xml’е и предлагают с ней мучиться. И понять, отчего вот эта yaml’ина приводит к вот такой ошибке можно только построив модель всей системы в уме. Про автодополенние, подсказки и пр. поддержку среды вообще молчу.

ugoday ★★★★★
()

@masterOf мой ответ снесли со всей цепочкой, продублирую.

Подавляющее число HPC кодов которые я написал в последние 20 лет написаны на связке С++ и питон. Питон используется для интерфейса (управления расчетами), С++ для вычислительного ядра, где собственно все данные и вычисления и крутятся. Биндинг через swig.

Кроме того питон используется у нас ещё в куче мест, но конечно никто на нем непосредственно большие объёмы данных не лопатит.

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

На питоне у нас очень активно делается всякая кодогенерация. Целевые ЯП С++, гнуплот, свои байтмашины. Когда то старший коллега попытался перейти на хаскель, год ним баловался, даже какой то кодогенератор написал, но потом вернулся на питон.

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

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

Вот это и показатель. Применение настроек Emacs подразумевает как минимум базу! А это не хухры-мухры. На каждый чих есть название функция. Или костылить. Короче, за опыт я это не считаю.

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

Для того, чтобы баловаться, надо мозг слегка провернуть против часовой стрелки. А у него всезде Си и плюсы. Как это ему сделать? Задачи те же, да подход другой. Вот и не зашло. А что это показывает? Сатори не произошло, буду всем говорить, что «говно эти ваши штангисты. Считаем и считали. На Си». Только перемалывать большие данные на неленивом языке…

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

Только перемалывать большие данные на неленивом языке…

Haskell и большие данные? Там чтобы json потоком разобрать за вменяемую память буквально предлагают пердолить attoparsec и flatparse.

cumvillain
()
Последнее исправление: cumvillain (всего исправлений: 2)

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

пишу продакшн на лиспе больше 5 лет, как хорошо что в индустрии данный подход применяется нечасто. Макросы в лиспе это баг, а не фича

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

люди которые там клоунов поставили, имеют кругозор уровня плинтуса.

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

Clojure - абсолютно отдельный язык, и судить по нему о лиспах, это то же самое что судить о С# по сишечке или плюсам

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

Чтобы язык стал популярным в 2023 году, придётся решать эти весьма нетривиальные проблемы.

В 2023 всем плевать на ИДЕ, все новые языки созданы, чтобы писать в блокноте.

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

Да не только мое мнение. Об это сам Столлман говорил, как и куча других людей.

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

Как я уже выше писал, лисп это в частности специфический рантайм, порядок вычисления, и среда выполнения. Отсюда идут всякие там EVAL-WHEN и сотоварищи. Где в Clojure вообще EVAL-WHEN?

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

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

Люди просто мыслят адски узко. Вообще не понимая о чем они говорят.

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

Где в Clojure вообще EVAL-WHEN?

Зочем?

There is no separate compilation step, nor any need to worry that a function you have defined is being interpreted. Clojure has no interpreter

https://clojure.org/reference/evaluation

Люди, которые считают, что Clojure это ни хера не лисп, ориентируются вообще на какую-то ни хера не значащую мелочь

Починил, не благодари

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

Люди просто мыслят адски узко.

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

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

интерпретатор тут не при чем

Судя по тому, что тут написано, в секции EVAL-WHEN

There are three possible situations — :compile-toplevel, :load-toplevel, and :execute — and which ones you specify controls when the body-forms will be evaluated.

Коммон Лисп может делать load как с компиляцией, так и без.

Насколько я понимаю, кложа не может делать load исходников (в ней нет интерпретатора), поэтому перед load всегда идёт compile (в байткод, JIT или AOT), а значит, нет смысла рассматривать load без compile. Следовательно, eval-when не нужен.

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

Коммон Лисп может делать load как с компиляцией, так и без.

Компиляция в стандарте CL это не то что ты думаешь, это первое.

Второе, SBCL всегда компилирует код, в том смысле в котором ты подразумеваешь, даже в load.

Насколько я понимаю, кложа не может делать load исходников (в ней нет интерпретатора), поэтому перед load всегда идёт compile (в байткод, JIT или AOT), а значит, нет смысла рассматривать load без compile. Следовательно, eval-when не нужен.

Лол. Я говорю, учи матчасть.

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

кстати, если дальше в дебри забираться, еще есть например LOAD-TIME-VALUE, но это вообще наверное недоступно для понимания кложурщикам и прочим джаваскриптерам

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

Касательно нужности eval-when.

Вот у меня есть такие два макроса, в моей библиотеке:

https://github.com/Lovesan/bike/blob/a23158c0c8114524df989d6d0670354af2b3cba9/src/internals-ffi.lisp#L38

;; LOAD-FOREIGN-LIBRARY actually closes the library before
;;  reloading it again. That would lead to a crash in case of .NET runtime
;;  being present in the process. .NET runtime, once loaded, can not be
;;  unloaded without proper deinitialization. Moreover, it can not be
;;  loaded again, once it has been shut down and/or the coreclr
;;  library has been unloaded.
;; Now, this could be mitigated by using the FOREIGN-LIBRARY-LOADED-P function
;;  from CFFI except for the fact that CFFI redefines foreign libraries each time
;;  DEFINE-FOREIGN-LIBRARY expansion is evaluated.
;;  When it does this, it omits library load status
;;  making the FOREIGN-LIBRARY-LOADED-P function effectively useless.
(defmacro define-foreign-library-once (name-and-options &body pairs)
  "Defines a foreign library NAME that can be posteriorly used with
    the USE-FOREIGN-LIBRARY macro.
Unlike the original CFFI macro, it does not redefine the library
  in case it is already defined."
  (let ((name-and-options (ensure-list name-and-options)))
    (destructuring-bind (name &rest options)
        name-and-options
      `(eval-when (:compile-toplevel :load-toplevel :execute)
         (unless (find ',name (list-foreign-libraries :loaded-only nil)
                       :key #'foreign-library-name)
           (define-foreign-library (,name ,@options)
             ,@pairs))))))

(defmacro use-foreign-library-once (name)
  (declare (type (and symbol (not null)) name))
  `(eval-when (:compile-toplevel :load-toplevel :execute)
     (unless (foreign-library-loaded-p ',name)
       (load-foreign-library ',name))))

Внимание вопрос - зачем это делается, и как это повторить в Clojure?

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

Но он где то слышал что лисп и ленивые вычисления это афигенно круто!

Видимо там же, где рассказывают, что лисп имеет какое-то отношение к ленивым вычислениям 😊.

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

Компиляция в стандарте CL это не то что ты думаешь, это первое.

Я вообще не думаю про стандарт CL.

Лол. Я говорю, учи матчасть.

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

Заучивание наизусть количества гаек на блоке цилиндров ГАЗ-66 поможет мне стать лучшим автомехаником? Или отличное от описанного в документации на ГАЗ-66 количество гаек мешает автомобилю быть автомобилем? А если там винты вместо гаек, то вообще туши свет?

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

зачем это делается, и как это повторить в Clojure?

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

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

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

Все популярные языки имеют крутую поддержку в IDE. А новые они или старые это уже второй вопрос.

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

Знаю отличную историю, как один водила старого ПАЗ не мог поменять левое переднее колесо, потому что не знал, что там, именно на этом колесе, левая резьба. А он думал, правая. А на правом переднем колесе резьба, кстати, правая. Затейники какие конструкторы были, а? 🤣

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

как в Clojure делаются сайд-эффекты времени компиляции?

Примерчик бы привёл простенькой, штоле. Без экранов кода на лиспе, просто словами — надо сделать, чтобы было вот так. А то я не очень улавливаю, чем сайд-эффекты времени компиляции принципиально отличаются от сайд-эффектов времени выполнения и почему это так важно. Учитывая, что в кложе выполнения без компиляции не бывает.

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

один водила старого ПАЗ не мог поменять левое переднее колесо, потому что не знал, что там, именно на этом колесе, левая резьба

Прохладная былина. Как это знание поможет тому, кто не водит ПАЗ и у кого резьба на всех колёсах одинаковая?

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

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defvar *defined-enums* (make-hash-table))
  (defun ensure-enum (name items)
    (setf (gethash name *defined-enums*)
          (loop :for item :in items
                :for i :from 0
                :collect (cons item i))))
  (defun enum-value (name item-name)
    (cdr (assoc item-name (gethash name *defined-enums*)))))

(defmacro define-enum (name &rest items)
  (check-type name symbol)
  `(eval-when (:compile-toplevel :load-toplevel :execute)
     (ensure-enum ',name ',items)
     ',name))

(defmacro foo ()
  (let ((value (enum-value 'my-enum 'foo)))
    (format t "Compile time value of my-enum.foo: ~a~%" value)
    `(quote ,value)))

(define-enum my-enum foo bar baz)

(foo)

(define-enum my-enum bar foo baz)

(foo)
Compile time value of my-enum.foo: 0
Compile time value of my-enum.foo: 1
lovesan ★★
()
Ответ на: комментарий от Nervous

Не может разумеется. Кто ясно мыслит, тот ясно излагает. Кто мыслит как Лавсан, тот не то что изложить не может - даже энкодер написать не в состоянии…

AntonI ★★★★★
()

Мне кажется идея генерировать код тем же языком, которым он пишется, на 100% естественной и удачной

А потом оказывается, что у каждой библиотеки свой собственный недоDSL, который тоже нужно учить и который отвратительно комбинируется со всем остальным. Как, собственно, в CL, или теперь уже и в расте.

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

у каждой библиотеки свой собственный недоDSL, который тоже нужно учить и который отвратительно комбинируется со всем остальным

Ну что ж, полдела сделано — у нас есть какое-никакое разнообразие библиотек. Естественный отбор — твой выход %)

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

Каждое API это DSL

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

Один луп в коммон лиспе чего стоит.

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

там все ясно как день

Там куча кода, но нет самого главного — зачем этот код. Какую задачу он решает.

Практическая польза не обязательна, не диплом пишешь. В общих чертах хотя бы — например, выполнить такой-то код в компайл-тайме, а такой-то в рантайме.

Телепатически реверс-инжинирить твои портянки, да ещё в канун нового года, как-то нет желания.

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

интерпретаторы конфигов на JSON

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

Хотя на безрыбье и ямл сойдёт.

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

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

Макросы это просто функции

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

Если непонятно что там и где - тогда зачем ты вообще говоришь про лисп в контексте «clojure это лисп»? Если выходит что лисп ты не понимаешь?

Макрос define-enum это стандартный макрос вида define-чонить. Они примерно все так пишутся.

Макрос foo же просто показывает как изменяется сущность enum, определяемая в процессе компиляции, макросом define-enum

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