LINUX.ORG.RU

[Lisp] туплю с defmacro

 


0

2

Есть код:


(defun get-action-name (list)
	(mapcar #'(lambda (def-list) (list (first def-list) (second def-list))) list))

(defmacro with-new-actions (parent actions-list
							&body body)
  (get-action-name actions-list))

(defparameter *workspace-actions-list* 
  '((aNew "&New" #'new-page-handler "tab- new" "Ctrl+N")
	(aOpen "&Open" #'open-page-handler "open" "Ctrl+O")))

(with-new-actions 'parent *workspace-actions-list*) ;; 1

(with-new-actions 'parent ((aNew "&New" #'new-page-handler "tab- new" "Ctrl+N")
	(aOpen "&Open" #'open-page-handler "open" "Ctrl+O"))) ;; 2

Проблема в том что строка (2) раскрывается, а строка (1) - нет.

Вопрос как раскрыть *workspace-actions-list* внутри defmacro ? Google молчит, вот туплю и мучаюсь.

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

> Ты так говоришь, как будто у тебя есть собственный, уже почти законченный компилятор.
Нет, не компилятор, но я умею переносимо ставить хук на момент чтения символа из потока. Т.е., предотвратить внедрение мусорного символа в пакет я могу. Предотвратить переопределение смысла существующего символа, как правило, можно в рамках реализации лиспа. Есть ещё разные неудобства, типа отсутствия import as, но это тоже преодолимо с помощью того же хука.

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

> Это - SQL (alter table, alter procedure).

и что будет с таким сценарием: есть таблица с полем A. Есть хранимка, зависящая от поля А. Делаем alter table, удаляя поле A. Что будет с процедурой?

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

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

>и что будет с таким сценарием: есть таблица с полем A. Есть хранимка, >зависящая от поля А. Делаем alter table, удаляя поле A. Что будет с >процедурой?
Будет exception при попытке обратиться к полю. Но сервер не рухнет, рухнет только обратившийся запрос, ситуация останется под контролем.

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

Почему так происходит понятно

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

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

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

Есть таблица sysdepends, которая заполнена информацией о ссылках объектов друг на друга. Есть кеш компилированного (байт) кода. При изменении объекта кеш сбрасывается для зависимых объектов. Далее, любая процедура компилируется, если она не откомпилирована, перед исполнением. Типа JIT. Ничего сложного и может быть реализовано даже для C++ (при соответствующем устройстве рантайм-среды). Хотя в MS SQL есть бага - если меняешь определение таблицы, ссылающиеся на неё view в какой-то из версий протухали и их надо было вручную пересоздавать.

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

> В CL это не проблема. Не проблема то, что программа становится некорректной в режиме разработки. А в С++ это проблема, ибо программа просто грохнется.

в итоге получается 2 версии программы: одна поломанная в образе (которая станет рабочей, когда изменения программы будут выполнены целиком, как транзакция) и одна, которую можно собрать из исходников. В настоящей эволюционной разработке первого «не целостного состояния» не должно быть. Как с бранчами в git: после коммита «транзакции» состояние целостное, work in progress либо не коммитится, либо сливается в отдельный changeset.
Теперь, допустим, было бы не 1, а 2 рабочих образа, отдельные «миры», и возможность переключаться между ними, так же легко, как создаются временные feature branch. Из них один всегда рабочий, целостный, а второй — work in progress.
В каком-то из смоллтоков такая фича есть — «миры» можно клонировать.

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


но все такие изменения делаются как транзакция, иначе толку от них не будет.

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

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

но все такие изменения делаются как транзакция, иначе толку от них не будет.

В теории - да. На практике - необязательно.

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

[quote] Например, есть ли аналог in-package, чтобы я мог попасть в контекст того модуля, где эта функция была определена? Ведь она может ссылаться на какие-то частные функции и переменные этого модуля. Если я в REPL нахожусь в другом пр-ве имён, то толку от перекомпиляции никакого не будет. [/quote] Естественно нет. Это только в CL можно невозбранно засирать что угодно, в Racket все как у людей, для каждого модуля есть свой собственный неймспейс.

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

> Синтаксис backquote не связаны с макросами, хотя и часто используется в них.

странное утверждение. И правила `(... ,foo) = '(.. eval-foo) ; `(... foo) = '(... quote-foo) — тоже не связаны с макросами?
какая польза от `(... ) без , ?

ибо это просто не часть макросистемы.


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

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

Кстати, насчет инкрементальной компиляции - не надо путаться. В CL ее нету ровно в той же степени, что и в Racket. Подменять функции в других пакетах можно в CL только из-за ущербности системы пакетов - вместо того, чтобы выделять полноценные неймспейсы, ридер просто подставляет квалификатор к символам. Такую же систему пекеджей можно элементарно сделать и в Racket, и будут такие же in-package и прочие гадости, другое дело, что это никому не нужно, ракетовская система модулей гораздо более продуманна, чем пекедж-костыль.

anonymous
()
Ответ на: комментарий от anonymous
Можно так распарсить, чтобы int попадал в один класс, double в другой, string в третий,...

датумы - да.

Или, можно указать макросу -- тут принимаем только константы, тут выражения, тут функцию?

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

anonymous
()
Ответ на: комментарий от archimag
Синтаксис backquote не связаны с макросами, хотя и часто используется в них. Т.е. претензии к данному механизму в контексте обсуждения макросистемы безосновательным, ибо это просто не часть макросистемы.

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

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

> Кстати, насчет инкрементальной компиляции - не надо путаться. В CL ее нету ровно в той же степени, что и в Racket.
Если бы ты не был анонимусом, ты был бы уже в моём игнор листе. Ты порешь очевидную маркетроидную хрень. Иди учи матчасть. Во всяком случае, слив засчитан :-)

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

Синтаксис backquote не связаны с макросами, хотя и часто используется в них. Т.е. претензии к данному механизму в контексте обсуждения макросистемы безосновательным, ибо это просто не часть макросистемы.

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

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

> Подменять функции в других пакетах можно в CL только из-за ущербности

системы пакетов - вместо того, чтобы выделять полноценные неймспейсы,

ридер просто подставляет квалификатор к символам



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

Ну бекквоты необходимо использовать, иначе

как ты собрался макросы писать?



Макросы в CL это функции, которые вызываются на этапе компиляции, принимают s-выражение и преобразуют его в другое, которое подставляется в место вызова макроса. Каким образом макрос делает это преобразование - его личное дело. backquote часто удобны для создания s-выражений и только поэтому они часто используются в макросах.

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

гм... инкрементальная компиляция — это что, и какими фичами должен обладать язык и среда для её поддержки?

википедия говорит, что

а) термин многозначный, и его часто путают с интерактивной разработкой

б) есть инкрементальные компиляторы лиспа, схемы (Ikarus), gcc (gcc-server, статус проекта неясен)

в) есть инкрементальные линкеры , которые относятся скорее к инструментам сборки, так же как инкрементальные системы сборки

г) физически, под инкрементальной компиляцией понимают скорее hot-swapping

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

Свойства языка:

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

б) рефлексия, зеркала (mirrors). Аналогичное MOP в CLOS, но с другой объектной моделью

в) системы с одним образом и его проблемами

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

д) live code, то есть, интерактивная разработка

е) короткий цикл write-compile-run-test-debug

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

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

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

> надо определиться с тем, что мы понимаем под инкрементальной компиляцией для начала.

надо. И с тем, что в реальном процессе полезно, а что — не очень.

Если же мы понимаем под этим возможность написать программу, скомпилировать в некоторый файл, потом переписать одну единственную функцию и перекомпиляцией этой функции получить новый корректный скомпилированный файл без полной перекомпиляции (все это без репла, естественно), то да - в Racket такого нет. В CL есть?


в CL ты не будешь хотеть получать скомпилированный файл, который затем исполнять. Ты запустишь из рантайма лиспа встроенный в рантайм компилятор, который соберёт функцию, и заменит её в текущем образе в памяти, получив «живой код». При этом есть возможность такой образ сдампить в файл/загрузить из файла, есть возможность в некоторых лиспах сделать deliver конечного приложения, то есть, собрать исполняемый файл без лишнего балласта в рантайме.

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

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

Все можно, кроме дампа. Хотя нет, дамп тоже можно - достаточно сериализовать результат call/cc, получится то же самое :)

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

> Каким образом макрос делает это преобразование - его личное дело.

это дело, то есть, порядок вычисления его аргументов задаётся синтаксисом backquote. В стандарте описан порядок компиляции, и вычисления аргументов, функции (все слева направо), аргументов макроса (слева направо по коду текста макроса, «разквоченные» запятой внутри backquote).
Если порядок вычислений не соблюдается, макрос не может быть развернут :
`(if (= ,b 0) a (/ a b)) — «энергичное» вычисление / при b=0 привело бы к ошибке.

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

>Вопрос такой, каким фичами должен обладать а) язык программирования >б) процесс разработки, используемый программистом в) используемые >инструменты, чтобы начать от этого процесса получать практический >профит? Поделитесь ощущениями.
Фича состоит в том, что это должно быть предусмотрено дизайном языка.
В частности, для CL это возможно из-за:
- сборки мусора (нет утечек памяти, если ты просто бросил старый код)
- возможность избежать ошибок работы с памятью, если не (optimize (safety 0))
- first class symbols (можно стереть имя)
- first class namespaces (можно динамически создать, поменять, разрушить пространство имён)
- dynamic classes (класс и метод можно создать, уничтожить, поменять в рантайме, изменения отражаются на экземплярах)
- hot code swapping (автоматическая и безопасная подмена функции на новую)
- наличие в образе функции compile, и вообще возможность компиляции отдельной функции с привязкой к данным уже загруженного образа (например, в C уже одно это может вызвать страшные сложности)
- поддержка со стороны дебаггера, например, по умолчанию программа сегфолтится крайне редко; есть такие фичи, как restart frame, которые позволяют (иногда) перезапустить новое определение в том же цикле, в котором выявлена неполадка
- разного рода мелочи типа (with-open-file), гарантирующие (при определённой дисциплине) корректное закрытие потоков при исключениях.
- отчасти, многопоточностью (можно запустить второй поток и править что-то в нём, пока один поток остановлен в дебаггере)
- поддержкой IDE (SLIME)
- решение вопросов повторной инициализации, если подгружается модуль. Например, defvar, который срабатывает только один раз.
Таким образом, нужен комплекс взаимосвязанных мер, и, если интерактивная разработка не предусмотрена дизайном, то, наверное, её не так легко и получить. Хотя, я думаю, что в Питоне наверняка её можно сделать, просто никто не сделал по безграмотности, или никто на ЛОРе не знает.

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

Пакеты это не неймспейсы и не задумывались таким образом. Это фундаментальная часть системы поддержки символьных вычислений, на которых CL и основан.

Я боюсь представить, что тут подразумевается под «символьными вычислениями». Будем считать - что-то имеющее отношение к делу. Но в общем-то это все не важно. Тут просто начали утверждать, что Racket CANNOT INTO «инкрементальная компиляция», что бы под ней не подразумевали. Я счел нужным пояснить, что вполне CAN. Никаких проблем реализовать аналогичную CL систему пекеджей в ракете нет - а тогда все символы будут в одном неймспейсе и к ним можно будет спокойно обращаться и переопределять. Так же, как в CL.

Макросы в CL это функции, которые вызываются на этапе компиляции, принимают s-выражение и преобразуют его в другое, которое подставляется в место вызова макроса. Каким образом макрос делает это преобразование - его личное дело. backquote часто удобны для создания s-выражений и только поэтому они часто используются в макросах.

Ну это уже софистика. Макросистема - это не только собственно принципы работы макросов, но и средства для их написания тоже.

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

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

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

> Никаких проблем реализовать аналогичную CL систему пекеджей в ракете нет
Вопрос не в том. Пусть есть функция inner в модуле и в глобальном нейспесе есть другая ф-я inner.

Мы хотим переопредилить ф-ю outer, которая ссылается на inner.
Если мы тупо скопипастим исходник из модуля в repl, то новая outer будет ссылаться не на ту inner, на которую надо. Вот я и спрашиваю, есть ли способ это победить. Питоновцы в этом месте слились.

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

> это дело, то есть, порядок вычисления его аргументов

задаётся синтаксисом backquote.


При чём тут вообще backquote? Вы о чём вообще?

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

> это дело, то есть, порядок вычисления его аргументов задаётся синтаксисом backquote.

Нет, не задается. Backquote - это синтаксический сахар над quote и list.

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

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

А вот сделать intern, uintern, gensym не получится, да?

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

внезапно.. (compile 'fn '(lambda (params) (...new-body...)))?
не файл целиком (compile-file "..."), а одну конкретную функцию
а в некоторых лиспах ещё и compile-file-if-needed есть

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

> Макросистема - это не только собственно принципы работы макросов,

но и средства для их написания тоже.


backquote не является средством для создания макросов, а только средством для создания списком. Макросисема CL это defmacro и больше ничего. Внутри defmacro могу использоваться любые средства языка. Код макроса - это обычный код на CL, зачем нужны какие-то ещё дополнительные средства?

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

Мы хотим переопредилить ф-ю outer, которая ссылается на inner. Если мы тупо скопипастим исходник из модуля в repl, то новая outer будет ссылаться не на ту inner, на которую надо. Вот я и спрашиваю, есть ли способ это победить.

Ну в cl это, как я понимаю, решается через in-package - в каком пекедже находимся, ту outer и переопределяем. Соответственно пишем в репле (in-package *название пекеджа с outer*), тогда в новом определении будет использована та же inner, что и в первоначальном определение outer.

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

>> Макросистема - это

Макросисема CL


разные штуки, да.

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

А вот сделать intern, uintern, gensym не получится, да?

Это как раз самая простая часть, с чего ты взял, что тут какие-то проблемы есть? Собственно, основные сложности будут с реализацией аналогов provide, require и т.п. Технически ничего особого, но много.

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

> решается через in-package - в каком пекедже находимся

in-package всё, что делает - это присваивает значение переменной *package*.

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

внезапно.. (compile 'fn '(lambda (params) (...new-body...)))?

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

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

in-package всё, что делает - это присваивает значение переменной *package*.

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

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

Ну в ракете это будет файл с байткодом программы. Чего там у вас получается после компиляции в CL - не знаю.

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

> Нам надо новый файл получить без полной перекомпиляции, а не тупо в репле скомпилить.
Например, можно скомпилировать в другом лиспе fasl-файл (что-то типа .obj), а в целевом образе оставить только функцию (load). Но основной сценарий работы - другой. Вся разработка происходит интерактивно в одном образе. Что не мешает тому, чтобы отдельные функции/файлы были скомпилированы в нативный код. Когда надо сделать релиз, делается deliver и получается урезанная копия того же образа, в которой можно вырезать что надо, в т.ч. REPL. Если образ запорчен, то заново загружаем всё из сорсов, но это нужно не часто.

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

> Ну в ракете это будет файл с байткодом программы. Чего там у вас

получается после компиляции в CL - не знаю.


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

archimag ★★★
()
Ответ на: комментарий от archimag
од макроса - это обычный код на CL, зачем нужны какие-то ещё дополнительные средства?

Мы же если говорим о каком-то языке, то учитываем его стандартную библиотеку, а не только примитивы? Так и тут. А хотя я вобщем-то понял в чем проблема. В cl макрос получает список и возвращает список. В ракете макрос получает синтаксический объект и возвращает синтаксический объект. Имеет смысл говорить о специальных средствах для работы с синтаксическими объектами, но спец. средства для работы со списками? В ЛИСПЕ? То есть никаких таких специальных средств для макросов в CL нету и быть не может - только стандартные языковые конструкции.

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

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

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

Спасибо, конечно. Претензии были именно в необходимости перехеширования. Читать не буду (многабукф), скажу только, что в условиях сборки мусора, в любом случае не удастся избежать потерь производительности, если существуют и долгоживущие, и короткоживущие объекты и если они ссылаются друг на друга. Сама поколенческая сборка мусора не достаётся даром - удорожается такая операция, как копирование ссылки, т.к. нужно вести учёт ссылок между поколениями. В общем, без явного управления памятью предельная производительность недостижима. Где-то на свете есть (по-моему, весьма сложные) open-source проекты, которые реализуют безопасное смешанное управление памятью.

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

> Ну вот у нас есть фасл файл

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

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

не, в CL не так. Тут висит постоянно загруженный образ в памяти, и после compile заменяется у символа объект типа compiled-function, после чего (compiled-function-p #'foo) выдаёт t. Откомпилированный код хранится привязанным к символу, в его plist.

теоретически можно поставить и словить рестарты на ошибки конпеляции, практически не видел, чтобы кто-нибудь этим пользовался. а compile-file и file compier это если весь файл целиком

If a non-nil name is given, then the resulting compiled function replaces the existing function definition of name and the name is returned as the primary value; if name is a symbol that names a macro, its macro function is updated and the name is returned as the primary value.

Literal objects appearing in code processed by the compile function are neither copied nor coalesced. The code resulting from the execution of compile references objects that are eql to the corresponding objects in the source code.

compile is permitted, but not required, to establish a handler for conditions of type error. For example, the handler might issue a warning and restart compilation from some implementation-dependent point in order to let the compilation proceed without manual intervention.

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

> Ну вот у нас есть фасл файл, мы в его исходниках поменяли одну >функцию - можно ли теперь получить новый фасл файл из старого >скомпилировав только новую функцию и ничего больше? Я об этом >спрашивал.
Теоретически, можно записать эту функцию в текстовый файл и сделать ему compile-file. Получится fasl с одной функцией. Если в целевом образе есть хотя бы функция load, она может подцепить этот файл. Но в реальности у нас обычно есть _один_ образ, зачем больше-то? Он - и среда разработки, и среда исполнения.

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

вопрос тем, у кого при переопределении макроса его call sites ломаются. А чего бы не повесить хук на такой рестарт?

if name is a symbol that names a macro, its macro function is updated and the name is returned as the primary value.

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