LINUX.ORG.RU

Вопрос про гигиенические макросы

 , , , ,


2

4

Здравствуйте, мои маленькие любители макросов!

Есть такой макрос на CL:

(defun has-tag-p (tag record) ... )

(defmacro select (query records)
  (let ((rec (gensym "record")))
    (labels ((query-helper (q)
               (if (and (listp q)
                        (member (car q) '(and or not)))
                   `(,(car q) ,@(mapcar #'query-helper (cdr q)))
                   `(has-tag-p ,q ,rec))))
      `(remove-if-not (lambda (,rec) ,(query-helper query)) ,records))))

Аналогичный макрос (без гигиены) на guile:

(define (has-tag? tag record) ... )

(define-macro (select query records)
  (define rec (gensym "record"))
  (define (query-helper q)
    (if (and (list? q)
             (memq (car q) '(and or not)))
        `(,(car q) ,@(map query-helper (cdr q)))
        `(has-tag? ,q ,rec)))
  `(filter (lambda (,rec) ,(query-helper query)) ,records))

Вопрос: как написать такое же, но с гигиеной, используя (1) только стандарт R5RS, (2) стандарт R7RS, (3) Racket?

Призываю @monk’а и прочих знатоков Scheme.

Ну и с интересом выслушаю замечания бывалых лисперов по приведённому коду.



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

Зачем экспортировать has-tag-p ?

Например, он тоже часть API.

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

Во время разработки много народу не нужно

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

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

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

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

Я про опасность переопределения с использованием макроса внутри тела flet.

Тут согласен. Или префикс у пользователя библиотеки или дополнительные телодвижения у разработчика библиотеки.

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

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

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

А это точно отработает при загрузке скомпилированного кода?

Точно.

И второй вопрос: если стоит ... , то точно раскрытие макроса и привязка (fdefinition f-has-tag-p) произойдёт до того, как has-tag-p испортится?

Точно.

ЗЫ Этот макрос запутан из за рекурсивных сборок квазицитат, поэтому в нем сложно сделать использование has-tag-p в самом макросе с тем же именем. Если хочешь, могу дать пример на вопрос из другой ветки.

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

КАДы и БД побольше браузера будут. Автокад и Постгрес изначально были написаны на лиспе. Кстати браузер на лиспе тоже пишут. И на лиспе требуется гораздо меньше разрабов для аналогичной работы.

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

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

Точно

Я ж не поленюсь, проверю... но чуть позже.

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

Там-то как раз всё понятно. Там претензия была к тому, что правильной названа версия без защиты локального имени.

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

То есть общелисп не для программ, а для прототипов. Тогда согласен.

Есть ещё микрониша: программы для инженеров. Типа Emacs'а. Когда пользователь точно является программистом. Тогда общелисповские кишки наружу становятся преимуществом, а не недостатком.

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

Или для прототипов, или для когда требования постоянно меняются. Для стабильных программ мощности избыточны, как и требования к квалификации программистов.

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

Точно

Как и предполагалось:

$ cat select.lisp
(defpackage :select (:use :cl) (:export :select :has-tag-p))
(in-package :select)

(defun has-tag-p (f rec) (member f rec))

(defmacro select (query records)
  (let ((rec (gensym "RECORD"))
        (f-has-tag-p (gensym)))
    (setf (fdefinition f-has-tag-p) #'has-tag-p)
    (labels ((query-helper (q)
               (if (and (listp q)
                        (member (car q) '(and or not)))
                   `(,(car q) ,@(mapcar #'query-helper (cdr q)))
                   `(,f-has-tag-p ,q ,rec))))
      `(remove-if-not (lambda (,rec)
                        ,(query-helper query))
                      ,records))))
$ cat use.lisp
(defpackage :test (:use :cl :select))
(in-package :test)

(defvar *data* '((a b c) (b c) (a b)))

(defun test ()
  (select (and 'a 'b) *data*))

(test)
$ sbcl
This is SBCL 1.4.16.debian, an implementation of ANSI Common Lisp.
...
* (compile-file "select.lisp")
; compiling file "/home/monk/tmp/select.lisp" (written 13 AUG 2019 09:17:49 PM):
; compiling (DEFPACKAGE :SELECT ...)
; compiling (IN-PACKAGE :SELECT)
; compiling (DEFUN HAS-TAG-P ...)
; compiling (DEFMACRO SELECT ...)

; wrote /home/monk/tmp/select.fasl
; compilation finished in 0:00:00.014
#P"/home/monk/tmp/select.fasl"
NIL
NIL
* (load "select.fasl")
T
* (compile-file "use.lisp")
; compiling file "/home/monk/tmp/use.lisp" (written 13 AUG 2019 09:24:23 PM):
; compiling (DEFPACKAGE :TEST ...)
; compiling (IN-PACKAGE :TEST)
; compiling (DEFVAR *DATA* ...)
; compiling (DEFUN TEST ...)
; compiling (TEST)

; wrote /home/monk/tmp/use.fasl
; compilation finished in 0:00:00.002
#P"/home/monk/tmp/use.fasl"
NIL
NIL
*
$ sbcl
This is SBCL 1.4.16.debian, an implementation of ANSI Common Lisp.
...
* (load "select.fasl")
T
* (load "use.fasl")

debugger invoked on a UNDEFINED-FUNCTION in thread
#<THREAD "main thread" RUNNING {10005D05B3}>:
  The function #:G1 is undefined.

Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [CONTINUE      ] Retry calling #:G1.
  1: [USE-VALUE     ] Call specified function.
  2: [RETURN-VALUE  ] Return specified values.
  3: [RETURN-NOTHING] Return zero values.
  4: [ABORT         ] Exit debugger, returning to top level.

("undefined function" A (A B C))
monk ★★★★★
()
Ответ на: комментарий от monk

Точно, с фаслами «в лоб» не всегда проходит, надо как-то по другому. (

Будет время - переделаю чтобы заработало.

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

Точно, с фаслами «в лоб» не всегда проходит, надо как-то по другому. (

Это ещё одна причина, почему CL для инженеров. Очень легко сделать ошибку, которая воспроизводится только у клиента (потому что разработчик запускает из SLIME, а клиент загружает фаслы или бинарник).

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

Это ещё одна причина, почему CL для инженеров.

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

А вот какие у CL есть достоинства по сравнению со Scheme, из-за которых его захотелось бы использовать (инженерам там или ещё кому-то)?

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

А вот какие у CL есть достоинства по сравнению со Scheme, из-за которых его захотелось бы использовать (инженерам там или ещё кому-то)?

1. Основное достоинство: разработка в образе.

На большинстве языков после исправления исходного кода модуля требуется перезапуск. На CL основной методом разработки является изменение функций без выхода из программы. То есть надо тебе написать некую программу, которая вычисляет (h (g (f data))). Ты прямо в образе в командной строке лиспа вносишь тестовые данные data. Запускаешь (f data). Получаешь ошибку. Исправляешь f, загружаешь новую версию (через прямую вставку defun, перезагрузку файла через load или интеграцию с текстовым редактором). Главное, что data не изменился, его не надо вводить заново. Снова запускаешь (f data). Снова исправляешь. Наконец f отладил. Результат f тут же сохраняешь в промежуточную переменную и тут же начинаешь писать g. Если f выполняется достаточно долго, то промежуточные данные очень экономят время.

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

2. Мощный отладчик. Продолжение предыдущего преимущества. Если произошла любая ошибка, то из отладчика можно помимо стандартного просмотра данных на любом уровне стека, также выполнить любую команду, в том числе изменить любую функцию, если же программа в цикле, то его можно написать так, чтобы из отладчика можно было продолжить со следующей итерации (после исправления вручную данных и исправления вызываемой в цикле функции).

3. Очень гибкое ООП (CLOS). Наличие множественной диспетчеризации и метаклассов позволяет 90% паттернов GoF делать однострочниками, а также уточнять методы класса из любого пакета.

Вот ещё чуть-чуть: http://lisper.ru/articles/common-lisp-technologies

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

Разработка в образе делает неповторимую отладку, в Racket рекомендуют писать тесты.

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

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

Но скорость разработки от идеи до как-то работающего прототипа, разумеется, на CL выше.

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

по сравнению со Scheme

ООП в стиле CL в Scheme есть: GOOPS (Guile), GLS (Racket), Swindle (Racket), Tiny-CLOS (MIT), ..., но популярностью не пользуется. Слишком оно там чужеродно.

Вот, так сказать, иллюстрация (взгляд со стороны CL на Scheme): https://news.ycombinator.com/item?id=14251958

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

Точно, с фаслами «в лоб» не всегда проходит, надо как-то по другому. (

Это ещё одна причина, почему CL для инженеров. Очень легко сделать ошибку, которая воспроизводится только у клиента (потому что разработчик запускает из SLIME, а клиент загружает фаслы или бинарник).

Вспомнил, что за косяк с фаслами: Objects of type FUNCTION can't be dumped into fasl files. - лет 10 назад было обсуждение где-то в гугле. Это не решаемо. А учитывая, что совместимость фаслов не гарантирована не то, что для минорных версий, но даже для исправлений, использование фаслов - плохая идея.

С исходниками работает обычная загрузка

$ sbcl
This is SBCL 1.4.16.debian, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
* (load "select.lisp")
T
* (load "use.lisp")
T
* (test::test)
((TEST::A TEST::B TEST::C) (TEST::A TEST::B))
* (quit)

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

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

Вспомнил, что за косяк с фаслами: Objects of type FUNCTION can't be dumped into fasl files. - лет 10 назад было обсуждение где-то в гугле. Это не решаемо.

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

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

Предлагаете компилировать мир при каждом запуске? Вообще-то quicklisp/asdf построен именно на том, что load-op компилирует только при первой загрузке. И все библиотеки из quicklisp успешно переживают компиляцию в одном образе и загрузку в другом.

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

Достаточно поменять местами let и defmacro:

(let ((rec (gensym "RECORD"))
      (f-has-tag-p (gensym)))
  (setf (fdefinition f-has-tag-p) #'has-tag-p)
  (defmacro select (query records)
    (labels ((query-helper (q)
               (if (and (listp q)
                        (member (car q) '(and or not)))
                   `(,(car q) ,@(mapcar #'query-helper (cdr q)))
                   `(,f-has-tag-p ,q ,rec))))
      `(remove-if-not (lambda (,rec)
                        ,(query-helper query))
                      ,records))))

Так работает правильно. Вот такой вот «язык для инженеров».

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

Вот такой вот «язык для инженеров».

Тут скорее проблема в отсутствии документации. ( Я свои проги оформляю через квиклисп, а как он внутри устроен - разбираться лень. Тем более, с АСДФ. Поэтому фаслы мне не интересны. Когда припрет, тогда и буду разбираться.

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

Тут скорее проблема в отсутствии документации

ЩИТО???

http://www.lispworks.com/documentation/lw50/CLHS/Body/03_b.htm

И даже по-русски: http://lisper.ru/articles/eval-when

Я свои проги оформляю через квиклисп, а как он внутри устроен - разбираться лень. Тем более, с АСДФ. Поэтому фаслы мне не интересны.

facepalm.fasl

Ты же если свою прогу зальёшь в квиклисп, то у пользователей твоей проги ничего работать не будет (точнее будет ровно один раз). Именно потому что «фаслы не интересны». Речь ведь вообще не про фаслы, а про семантику компиляции и загрузки - основы CL.

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

Тут скорее проблема в отсутствии документации

ЩИТО???

На АСДФ. Линкфлай делал перевод, но там очень мутно(

А так, я оформляю по заветам Зака, а что внутри ... - главное работает.

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

О, какой подробный ответ, спасибо!

в командной строке лиспа вносишь тестовые данные data. Запускаешь (f data). Получаешь ошибку. Исправляешь f, загружаешь новую версию (через прямую вставку defun, перезагрузку файла через load или интеграцию с текстовым редактором). Главное, что data не изменился, его не надо вводить заново. Снова запускаешь (f data). Снова исправляешь. Наконец f отладил. Результат f тут же сохраняешь в промежуточную переменную и тут же начинаешь писать g. Если f выполняется достаточно долго, то промежуточные данные очень экономят время.

Да я, вроде бы, и в Chicken или Guile так же делаю (geiser, по ощущениям, менее крут, чем slime, но на это его хватает). Или есть какие-то тонкие отличия?

Пытался тут понять точку зрения Matthew «the top-level is hopeless» Flatt’а, но пока всей глубины этой безнадёжности не осознал.

Разработка в образе делает неповторимую отладку

Моего внутреннего control freak’а эта «неповторимость», конечно же, пугает.

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

Да я, вроде бы, и в Chicken или Guile так же делаю (geiser, по ощущениям, менее крут, чем slime, но на это его хватает). Или есть какие-то тонкие отличия?

Если у тебя через import подключен модуль и в нём обнаружилась ошибка, сможешь исправить её без перезапуска?

Если 4 часа отлаживал и надо перезагрузить компьютер, сможешь сохранить состояние образа и продолжить с того же места?

Если на оба вопроса ответ «да», то по п.1 существенных отличий нет. В Racket по обоим пунктам ответ «нет».

но пока всей глубины этой безнадёжности не осознал

Это как раз про то, что выполнение команд в REPL не совпадает с результатом при компиляции. Если для CL это в порядке вещей: нормальный инженер прочитает документацию и обойдёт грабли, то для Scheme это крайне неприятно. Собственно, это ещё одна причина, почему разработка в REPL в Racket не поощряется. При разработке через файлы каждый файл является модулем и этой проблемы не существует (если есть define, он влияет не только на последующие команды, но и на предыдущие).

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

На АСДФ. Линкфлай делал перевод, но там очень мутно(

А... Так у тебя проблема была не с АСДФ, а семантикой компиляции.

Вот с этим:

Чередование стадий вычисления происходит от того, что код обрабатывается последовательно, форма за формой; каждая форма верхнего уровня (top-level form) проходит стадии обработки кода, и только затем читается следующая форма. Это дает возможность производить какие-либо побочные эффекты, которые могут повлиять на обработку следующей формы. Например, если файл компилируется с помощью compile-file, то каждая форма проходит следующие стадии: чтение, раскрытие макросов, компиляция, и только при вызове load для скомпилированного fasl'а будут произведены эффекты времени загрузки; если файл загружается с помощью load, то каждая форма проходит через стадии: чтение, раскрытие макросов, компиляция, загрузка; если формы набираются в REPL, то форма проходит все стадии от чтения до исполнения. Поэтому, в зависимости от способа ввода кода (ввод в REPL; загрузка с помощью LOAD; компиляция и загрузка с помощью (LOAD (COMPILE-FILE ..)); вызов EVAL или COMPILE для формы), эффекты от него могут быть различными, так как побочные эффекты от разных форм будут наступать в разное время (чаще всего, разница будет в том, что будут ошибки компиляции либо загрузки).

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

Возможно. Так уж получилось, что Евал-вен ни разу не понадобился, и в тонкости компиляции некогда было вникать - всё время требовалось срочно запустить прототип( Видимо, пришло время разобраться.

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

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

Кстати, оба твои варианта у меня не работают:

первый

$ cat select.lisp
(defpackage #:select (:use #:cl) (:export #:select #:has-tag-p))
(in-package #:select)

(defun has-tag-p (f rec) (member f rec))

(defmacro select (query records)
  (let ((rec (gensym "record"))
        (f-has-tag-p #'has-tag-p))
    (labels ((query-helper (q)
               (if (and (listp q)
                        (member (car q) '(and or not)))
                   `(,(car q) ,@(mapcar #'query-helper (cdr q)))
                   `(funcall ,f-has-tag-p ,q ,rec))))
      `(remove-if-not (lambda (,rec) ,(query-helper query)) ,records))))

$ cat use.lisp
(defpackage #:test (:use #:cl #:select))
(in-package #:test)

(defvar *data* '((a b c) (b c) (a b)))

(defun test ()
  (select (and 'a 'b) *data*))

(test)
$ sbcl
This is SBCL 1.4.16.debian, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
* (compile-file "select.lisp")
; compiling file "/home/tester/lisp/ql/select/select.lisp" (written 17 AUG 2019 03:10:12 PM):
; compiling (DEFPACKAGE #:SELECT ...)
; compiling (IN-PACKAGE #:SELECT)
; compiling (DEFUN HAS-TAG-P ...)
; compiling (DEFMACRO SELECT ...)

; wrote /home/tester/lisp/ql/select/select.fasl
; compilation finished in 0:00:00.050
#P"/home/tester/lisp/ql/select/select.fasl"
NIL
NIL
* (load *)
T
* (compile-file "use.lisp")
; compiling file "/home/tester/lisp/ql/select/use.lisp" (written 13 AUG 2019 08:30:53 PM):
; compiling (DEFPACKAGE #:TEST ...)
; compiling (IN-PACKAGE #:TEST)
; compiling (DEFVAR *DATA* ...)
; compiling (DEFUN TEST ...)
; file: /home/tester/lisp/ql/select/use.lisp
; in: DEFUN TEST
;     (SELECT:SELECT (AND 'TEST::A 'TEST::B) TEST::*DATA*)
; --> REMOVE-IF-NOT LAMBDA FUNCTION AND IF FUNCALL SB-C::%FUNCALL THE
; --> SB-KERNEL:%COERCE-CALLABLE-FOR-CALL
; ==>
;   #<FUNCTION SELECT:HAS-TAG-P>
;
; caught ERROR:
;   Objects of type FUNCTION can't be dumped into fasl files.

; --> REMOVE-IF-NOT LAMBDA FUNCTION AND IF FUNCALL SB-C::%FUNCALL THE
; ==>
;   (SB-KERNEL:%COERCE-CALLABLE-FOR-CALL #<FUNCTION SELECT:HAS-TAG-P>)
;
; note: The first argument never returns a value.

; --> REMOVE-IF-NOT LAMBDA FUNCTION AND IF FUNCALL SB-C::%FUNCALL THE
; --> SB-KERNEL:%COERCE-CALLABLE-FOR-CALL
; ==>
;   #<FUNCTION SELECT:HAS-TAG-P>
;
; caught ERROR:
;   Objects of type FUNCTION can't be dumped into fasl files.

; --> REMOVE-IF-NOT LAMBDA FUNCTION AND IF FUNCALL SB-C::%FUNCALL THE
; ==>
;   (SB-KERNEL:%COERCE-CALLABLE-FOR-CALL #<FUNCTION SELECT:HAS-TAG-P>)
;
; note: The first argument never returns a value.

;     'TEST::B
;
; note: deleting unreachable code

;     'TEST::A
;
; note: deleting unreachable code

; compiling (TEST);
; compilation unit finished
;   caught 2 ERROR conditions
;   printed 4 notes


; wrote /home/tester/lisp/ql/select/use.fasl
; compilation finished in 0:00:00.032
#P"/home/tester/lisp/ql/select/use.fasl"
T
T
*

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

второй

$ cat select.lisp
(defpackage #:select (:use #:cl) (:export #:select #:has-tag-p))
(in-package #:select)

(defun has-tag-p (f rec) (member f rec))

(let ((rec (gensym "RECORD"))
      (f-has-tag-p (gensym)))
  (setf (fdefinition f-has-tag-p) #'has-tag-p)
  (defmacro select (query records)
    (labels ((query-helper (q)
               (if (and (listp q)
                        (member (car q) '(and or not)))
                   `(,(car q) ,@(mapcar #'query-helper (cdr q)))
                   `(,f-has-tag-p ,q ,rec))))
      `(remove-if-not (lambda (,rec)
                        ,(query-helper query))
                      ,records))))

$ cat use.lisp
(defpackage #:test (:use #:cl #:select))
(in-package #:test)

(defvar *data* '((a b c) (b c) (a b)))

(defun test ()
  (select (and 'a 'b) *data*))

(test)
$ sbcl
This is SBCL 1.4.16.debian, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
* (compile-file "select.lisp")
; compiling file "/home/tester/lisp/ql/select/select.lisp" (written 17 AUG 2019 02:14:23 PM):
; compiling (DEFPACKAGE #:SELECT ...)
; compiling (IN-PACKAGE #:SELECT)
; compiling (DEFUN HAS-TAG-P ...)
; compiling (LET (# #) ...)

; wrote /home/tester/lisp/ql/select/select.fasl
; compilation finished in 0:00:00.037
#P"/home/tester/lisp/ql/select/select.fasl"
NIL
NIL
* (load *)
T
* (compile-file "use.lisp")
; compiling file "/home/tester/lisp/ql/select/use.lisp" (written 13 AUG 2019 08:30:53 PM):
; compiling (DEFPACKAGE #:TEST ...)
; compiling (IN-PACKAGE #:TEST)
; compiling (DEFVAR *DATA* ...)
; compiling (DEFUN TEST ...)
; compiling (TEST)

; wrote /home/tester/lisp/ql/select/use.fasl
; compilation finished in 0:00:00.013
#P"/home/tester/lisp/ql/select/use.fasl"
NIL
NIL
* (load *)

debugger invoked on a UNDEFINED-FUNCTION in thread
#<THREAD "main thread" RUNNING {97349329}>:
  The function #:G414 is undefined.

Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

("undefined function" A (A B C))
0] 0
* (quit)
$

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

Edit:

Сейчас по быстрому нашел что топ-левел макросы должны убирать в макроподстановку.

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

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

Да, сообразил, максимум, что можно сделать, это

(setf (fdefinition 'f-has-tag-p) #'has-tag-p)
(defmacro select (query records)
  (let ((rec (gensym "RECORD")))
    (labels ((query-helper (q)
               (if (and (listp q)
                        (member (car q) '(and or not)))
                   `(,(car q) ,@(mapcar #'query-helper (cdr q)))
                   `(f-has-tag-p ,q ,rec))))
      `(remove-if-not (lambda (,rec)
                        ,(query-helper query))
                      ,records))))

Защиты от намеренного переопределения через select::f-has-tag-p нет, от случайного через экспортированную функцию — есть.

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