LINUX.ORG.RU

Racket - отладчик макросов - жизнеспособен ли?

 ,


1

2

Вот читаю в мануале:

Warning: because of limitations in the way macro expansion is selectively hidden, the resulting syntax may not evaluate to the same result as the original syntax

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

★★★★★

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

А ты чего хотел обсудить собственно?)

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

Согласен с тобой в общей оценке Racket-а.

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

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

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

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

Собственно, я хотел бы, чтобы кто-то опроверг последний вывод, если этот вывод неверен.

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

убедился, что с отладчиком не всё гладко

Это было 5 лет назад.

Что касается «the resulting syntax may not evaluate to the same result as the original syntax», то причина в том, что «The macro stepper skips over the expansion of the macros you designate as opaque, but it still shows the expansion of their subterms.».

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

Например, в терминах CL:

(defmacro f () 1)

(defmacro g (x) (list ',x))

(g (f))

Если поставить раскрывать f и не раскрывать g, то получим (g 1), что даёт не тот результат, что (g (f)).

Решение принято намеренно и осознанно, так как конструкция let, например, по-умолчанию не раскрывается (её потроха мало кому нужны), а содержимое тела let хотелось бы видеть раскрытым.

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

Мне сразу показалось, что Racket пилят в основном студенты с преподавателями в рамках дипломов и курсовых.

Все утилиты, названия которых начинаются с BSD (включая BSD UNIX) тоже пилили студенты с преподавателями. Тем не менее, качество этих утилит таково, что альтернатив для BIND (Berkeley Internet Name Domain) и Berkeley DB до сих пор нет.

Так и Racket. Туда не ставят костыли ради продакшена, но на общую стабильность это влияет только положительно. Нет таких моментов как sb-alien::alien-lambda, который вообще никак не документирован, но активно используется.

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

Почему это сделано - вполне понятно, но тем не менее результат не устраивает. Для продакшена обычно интересует вопрос «почему в конкретном сложном случае макрос раскрылся не так, как ожидалось».

Соответственно, нужен не инструмент, который в целом показывает как работает macroexpand, внося дополнительную неопределённость, а инструмент, к-рый позволяет увидеть детали существующего случая, не внося в него изменений (я не говорю, что это просто и не ругаю Racket за это решение).

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

Ну я не знаю. Я посмотрел, как там стрелочками визуализируется, как исходники между собой соотносятся.

Вроде круто для факториалов, но как это будет работать в реальной жизни, когда код представляет из себя гигантскую простыню, а начало и конец стрелочки находятся в разных файлах или отстоят на сотни строк друг от друга?

Т.е., наверное, дело не в том, пилят ли студенты, а дело в том, кто у них третий попугай и какие цели он ставит перед собой.

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

Для продакшена обычно интересует вопрос «почему в конкретном сложном случае макрос раскрылся не так, как ожидалось».

Тогда при нераскрытии макроса запрещаешь раскрытие содержимого этого макроса. Или явно перечисляешь те макросы, которые не портят тело (defun, defmacro, bind, ...).

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

Вроде круто для факториалов, но как это будет работать в реальной жизни, когда код представляет из себя гигантскую простыню, а начало и конец стрелочки находятся в разных файлах или отстоят на сотни строк друг от друга?

Я написал на Racket несколько достаточно крупных программ (пару сайтов, скачиватель/анализатор данных с сайта, службу обмена сообщениями). Стрелочки помогают. Если в идентификаторе опечатался, то стрелки нет или показывает не в том направлении. В принципе, SLIME делает почти то же самое, выводя в строке состояния значение или параметры.

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

а дело в том, кто у них третий попугай и какие цели он ставит перед собой

???

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

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

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

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

Здесь же вроде брекпойнт поставить нельзя - сиди и шагай.

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

Ну в этом случае на самом деле нужно сделать trace macroexpand-1 и составить правильное условие.

В Racket expand не совсем cl:macroexpand. expand раскрывает все макросы в данной ему форме, а не только головную.

То есть в CL:

(defmacro my-or (a &rest r)
  (cond 
    ((null r) a) 
    (t `(let ((a1 ,a)) (if a1 a1 (my-or ,@r))))))

(macroexpand '(my-or a b)) ; => (LET ((R A)) (IF R R (MY-OR B)))

(my-or b) не раскрылось

В Racket

(define-syntax my-or
  (syntax-rules ()
    [(my-or a) a]
    [(my-or a . r) (let ([a1 a]) (if a1 a1 (my-or . r)))]))

(syntax->datum (expand '(my-or a b))) ; => '(let-values (((a1) a)) (if a1 a1 b))

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

Типа expand/step-text?

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

Различия в данном случае не столь важны.

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

expand/step-text рассматривает в изоляции.

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

Здесь же вроде брекпойнт поставить нельзя - сиди и шагай.

Здесь аналог брекпойнта — правой кнопкой по идентификатору и выбрать Hide или Show. Отсекает раскрытие соответствующей ветки.

В принципе, мне хватало. Хотя в https://github.com/Kalimehtar/binary-class/blob/master/binary-class/syntax.rkt извращений над синтаксисом было достаточно.

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

Если я правильно понял, то это антибрекпойнт, к-рый нужно ставить везде, кроме.

Да.

Хотя в целом в Racket процесс отладки не приветствуется. «Если ты начал отлаживать программу, значит ты перестал понимать, как она работает» (с).

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

Я воспринимаю данное утверждение как декларацию о негодности данного инструмента.

Это не так. Человеческий отладчик макросов - одно из главных преимуществ Racket по сравнению с другими лиспами в принципе.

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

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

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

Чтобы использовать инструмент - надо понимать как он работает. В данном случае - как именно происходит сокрытие шагов, и как происходит сам экспанд в Racket (нелинейно и в несколько проходов). Я лично за несколько лет использования ни разу не встречался с ситуацией, когда результаты экспандеры были неожиданными, даже в случае очень нетривиальных макросов.

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

Если я правильно понял, то это антибрекпойнт, к-рый нужно ставить везде, кроме.

Если надо раскрыть вложенную форму, то следует просто проставить show по вложенности, начиная с топ-левел формы, то есть: (f (x (y (x i)))), чтобы раскрыть (z i) - поставить show в f, x, y. Обычно никаких сложностей с этим нет.

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

следует просто проставить show по вложенности, начиная с топ-левел формы

Так проблема в том, что все остальные макросы, определённые в модуле, он всё равно раскрывает. Или есть галочка, чтобы скрыть не только библиотечные, а все?

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

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

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

В текущем модуле обычно много макросов не бывает.

https://github.com/Kalimehtar/binary-class-mp3/blob/master/binary-class/mp3.rkt

Предположим, мне надо найти раскрытие куска

(define-binary-class id3v2.3-tag% id3-tag%
    ((extended-header-size (optional u4 (extended? flags)))
     (extra-flags          (optional u2 (extended? flags)))
     (padding-size         (optional u4 (extended? flags)))
     (crc                  (optional u4 (crc? flags extra-flags)))
     (frames               (id3-frames size id3v2.3-frame%))))

терпения щёлкать не хватает.

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

терпения щёлкать не хватает.

бинарный поиск решает проблему

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

А еще можно врапнуть все, что не надо раскрывать, в begin. Тогда при сокрытии racket-макросов все, что внутри begin не будет раскрыто в степпере,

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

«Если ты начал отлаживать программу, значит ты перестал понимать, как она работает»

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

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

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

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

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

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

Насчёт перестал понимать - а что, если это чужая программа и я изначально не понимал?

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

Со стороны программистов спрос на отладчики есть.

От языка зависит. Скажем, для SQL спроса на отладчики практически нет. Или для Haskell. Или для TeX.

И мне лично они тоже нравятся.

Это привычка. Пока не перешёл с CL на Racket мне тоже нравились. На 1С тоже пользуюсь (потому что иначе в миллионе строк говнокода быстро не найти, что не так с пользовательскими данными).

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

Вот именно! Когда я стал писать на лиспе бизнес-логику, мгновенно стало ясно, что нужен не просто отладчик как в СL, но и пошаговый отладчик, как в 1С. Т.е. у отладчика есть своя область применения, и если отладчика нет, в этой области применения возникает дыра, и 1С побеждает. Поэтому говорить «ты не понимаешь программу» - это пропаганда.

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

Вот именно! Когда я стал писать на лиспе бизнес-логику, мгновенно стало ясно, что нужен не просто отладчик как в СL, но и пошаговый отладчик, как в 1С.

Поздоравлдяю, ты не умеешь писать хороший код.

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

в этой области применения возникает дыра, и 1С побеждает

Наличие отладчика в 1С провоцирует писать тонны говнокода без какой-либо документации, проверки входных данных и разбивки на функции. Также как излишне хороший графический редактор SQL запросов в нём же провоцирует писать SQL запросы на пару десятков тысяч строк.

Инструмент должен поощрять написание хорошего кода, а не плохого.

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

Кстати, если брать для примера именно 1С, то хороший отладчик провоцирует ещё одну плохую практику: отладку на рабочей базе.

Также как Common Lisp на Deep Space 1 позволил исправить программу на работающем космическом модуле, также 1С позволяет исправлять код на работающей базе. При этом отладка и исправление сделаны крайне удобно, а для того, чтобы сделать резервную копию или тестовую базу надо выгнать из базы всех пользователей и пару часов им не давать работать (выгрузка базы только монопольно). В результате в 95% случаев отладка кода производится прямо на рабочей базе.

Бухгалтерия, конечно, не АСУ ТП. Если программист навернул базу, то бухгалтеры просто возьмут копию на начало дня и вобьют данные заново. Но если ставить отладку как центральный элемент инфраструктуры языка, то в лучшем случае получится Common Lisp, в худшем — 1С. Надёжные программы на таком языке писать не будут.

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

Нда. Тут есть над чем подумать мне.

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

Из того что я знаю в 7-ке было #ЗагрузитьИзфайла и внешние обработки. С тех пор появилось что-то ещё? Можно ли менять код в самой конфигурации во время работы пользователей?

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

С тех пор появилось что-то ещё? Можно ли менять код в самой конфигурации во время работы пользователей?

Да. Много появилось.

Во-первых, всё, кроме структуры конфигурации можно менять динамически (то есть новую версию видят только те, кто перезапустил 1С после изменения).

Во-вторых, появилось чудо-кнопка «Останавливать при ошибке» — автоточка останова на любой ошибке.

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

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

Разве что, в отличие от CL, нельзя поменять код функции без перезапуска. Только можно выполнять через ВнешниеОбработки.Создать(ПолноеИмяФайла).НоваяВерсияФункции(....).

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

Нда, это из серии про две новости.

Хорошая новость: горячая замена реализована в решении от лидера отрасли и используется в 90% случаев изменения конфигурации, если я правильно тебя понял. Т.е. можно игнорировать тот факт, что очень многие люди не понимают, что горячая замена нужна, и считают, что она не нужна. Можно просто сменить целевую аудиторию.

Плохая новость: у Яра становится всё меньше конкурентных преимуществ.

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

Тогда вопрос лично к тебе, как к знатоку 1С: имеет ли смысл заниматься Яром вообще? Что-то кроме статической типизации осталось из существенных преимуществ?

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

горячая замена реализована в решении от лидера отрасли

Ну, называть это горячей заменой — слишком сильно. В этом контексте у нас весь web использует горячую замену (сайт продолжает работать и сессии не рвутся при замене страницы или файла .js на сервере). Без перезапуска сессии запустить новую функцию, как я уже сказал, только из внешней обработки.

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

Что-то кроме статической типизации осталось из существенных преимуществ?

Она у тебя опционально. Если с этой стороны подходить, то проще добавить типизацию к http://oscript.io/ (это язык 1С под лицензией MPL).

А в остальном: в 1С нет классов (вместо них используются обработки и общие модули, но наследования нет вообще), в 1С нет макросов (есть только Выполнить, то есть eval), в 1С нет библиотек в виде отдельных пакетов (это в oscript исправили).

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

Тогда я не совсем понял твой пункт 1, поскольку не понимаю, что такое «структура конфигурации». Верно ли, что изменения в конфигурации (в т.ч. изменение в теле конкретного метода или изменение его сигнатуры) видны только новым сессиям?

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

На 1С тоже пользуюсь (потому что иначе в миллионе строк говнокода быстро не найти, что не так с пользовательскими данными).

А ты не думаешь, что с миллионом строк Racket-кода ситуация была бы похожая?

Опять же, иногда приходится иметь дело с разным кодом и каждый раз переписывать его из-за непонятности не вариант.

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

Верно ли, что изменения в конфигурации (в т.ч. изменение в теле конкретного метода или изменение его сигнатуры) видны только новым сессиям?

Да. «новую версию видят только те, кто перезапустил 1С после изменения» (с),

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

А ты не думаешь, что с миллионом строк Racket-кода ситуация была бы похожая?

Не думаю. Условный миллион строк из библиотек Racket я уже использую. Портянок на несколько тысяч строк в одной функции не встречается совсем, интерфейс модулей документирован. Более того, если проблема именно с пользовательскими данными, то вместо точки останова в кишках библиотеки почти всегда получу ошибку контракта на границе модуля.

Опять же, иногда приходится иметь дело с разным кодом и каждый раз переписывать его из-за непонятности не вариант.

На С/С++ говнокода (в смысле огромной недокументированной портянки) сильно меньше именно из-за относительной сложности отладки.

Этот «разный код» ведь должен откуда-то появиться. Скажем, на Haskell вообще говнокодить не получается. Пока не продумаешь все пути движения данных и краевые случаи, типизация не проходит :-) А на 1С 90% кода можно писать не приходя в сознание. Как на бейсике. Или на Perl. Но в отличие от бейсика и перла, после написания не появляется желания привести в адекватный вид, так как в случае чего на живой системе можно отладить (а эксплуатация без программистов фирмы-франчайзи не предполагается).

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

Условный миллион строк из библиотек Racket я уже использую.

Так ведь это немного другoе. Ведь ты не учитываешь библиотечные функции в 1С?

Портянок на несколько тысяч строк в одной функции не встречается совсем, интерфейс модулей документирован.

Опять же, сравнение (условно) вылизанного библиотечного кода и «пользовательского» говнокода.

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

Скажем, на Haskell вообще говнокодить не получается.

Как сказать. У тебя, возможно, и не получается, но посади не особо заинтересованного в «правильности» человека решать задачу. В итоге будет не идиоматический код со всякими unsafePerformIO и т.д.

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

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

Подскажи пожалуйста, а то до меня не доходит. Что лучше использовать на практике, систему контрактов или сразу уходить в Typed Racket? Спасибо.

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

Что лучше использовать на практике, систему контрактов или сразу уходить в Typed Racket?

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

(->i ([x number?]
      [y (x) (>=/c x)])
     [result (x y) (and/c number? (>=/c (+ x y)))])

на Typed Racket не изобразишь.

Зато Typed Racket позволяет делать статическое доказательство корректности. Если программа скомпилировалась, то условия, налагаемые типами Typed Racket верны. И благодаря этому Typed Racket делает некоторые оптимизации: меняет функции на их версию, не делающую проверку типа аргумента. Получается чуть быстрее и без потери читаемости.

Если для модуля достаточно типов Typed Racket, то лучше Typed Racket. Но желательно сначала определить (продумать) контракты на API, а потом принимать решение, а не наоборот.

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

можно делать тип результата зависящим от значений аргументов

А можешь словами расписать пример код? А то я Racket-код читаю с трудом, но интересно.

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r
;;; данный контракт описывает функцию
(->i ([x number?]         ;; первый аргумент которой -- число
      [y (x) (>=/c x)])   ;; второй -- число не меньше, чем первый
     [result (x y) (and/c number? (>=/c (+ x y)))])
;;; и результат функции не меньше чем сумма аргументов
monk ★★★★★
()
Ответ на: комментарий от den73

И как его исполнение контролируется? Только динамически?

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

(define/contract f (listof (integer? . -> . integer?)) (list (lambda (x) x)))
(define g (car f))
(g 'fail)
При запуске
f: contract violation
  expected: integer?
  given: 'fail
  in: the 1st argument of
      an element of
      (listof (-> integer? integer?))
  contract from: (definition f)
  blaming: /home/monk/test.rkt
   (assuming the contract is correct)
  at: /home/monk/test.rkt:2.17

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

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

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

den73 ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.