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.

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

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

`(remove-if-not (lambda (,rec) ,(query-helper query))
                ,records))))

лучше читается, чем:

`(remove-if-not (lambda (,rec) ,(query-helper query)) ,records))))

Ещё бы я в таком коде использовал бы вместо car и cdr их алиазы first и rest.

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

R5RS:

(define-syntax select
  (let-syntax ((query-helper
                (syntax-rules (and or not)
                  ((_ (and arg ...) rec) (and (query-helper arg rec) ...))
                  ((_ (or arg ...) rec) (or (query-helper arg rec) ...))
                  ;; исправил ошибку версии CL, там not к списку будет считаться нормальным
                  ((_ (not arg) rec) (not (query-helper arg rec)))
                  ((_ query rec) (has-tag-p query rec)))))
    (syntax-rules ()
      ((select query record)
       (filter (lambda (rec) (query-helper query rec)) records)))))
monk ★★★★★ ()
Ответ на: комментарий от monk

Поправка (плохо заливать без тестирования):

(define-syntax select
  (syntax-rules ()
    ((select query records)
     (... (letrec-syntax ((query-helper
                           (syntax-rules (and or not)
                             ((_ (and arg ...) rec) (and (query-helper arg rec) ...))
                             ((_ (or arg ...) rec) (or (query-helper arg rec) ...))
                             ;; исправил ошибку версии CL, там not к списку будет считаться нормальным
                             ((_ (not arg) rec) (not (query-helper arg rec)))
                             ((_ q rec) (has-tag-p q rec)))))
            (filter (lambda (rec) (query-helper query rec)) records))))))
monk ★★★★★ ()
Ответ на: комментарий от monk

Для Racket

(require (for-syntax syntax/parse) racket/stxparam)

(define-syntax-rule (select query records)
  (...
   (begin
     (define-syntax-parameter rec (syntax-rules ()))
     (define-syntax (query-helper stx)
       (syntax-parse stx
         #:literals (and or not)         
         [(_ ((~and op (~or and or)) arg ...)) #'(op (query-helper arg) ...)]
         [(_ (not arg)) #'(not (query-helper arg))]
         [(_ q) #'(has-tag-p q rec)]))
     (filter (lambda (r)
               (syntax-parameterize ([rec (syntax-id-rules () [_ r])])
                 (query-helper query)))
             records))))

Внутри query-helper'а не надо тащить rec и при этом он защищён механизмом гигиены.

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

Большое спасибо! Теперь буду разбираться, как это работает. А то всё никак не мог понять, как запихнуть в define-syntax рекурсию.

В Guile всё ок, а вот Chicken, почему-то, говорит

Error: (#f) "during expansion of (syntax-rules ...) - too many ellipses": ((arg ...))

Хотя в Chicken’е заявлена полная поддержка R5RS.

там not к списку будет считаться нормальным

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

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

Если не пытаться экономить строчку на объединении and и or, то будет

(define-syntax-rule (select query records)
  (...
   (begin
     (define-syntax-parameter rec (syntax-rules ()))
     (define-syntax query-helper       
       (syntax-rules (and or not)         
         [(_ (or arg ...)) (or (query-helper arg) ...)]
         [(_ (and arg ...)) (and (query-helper arg) ...)]
         [(_ (not arg)) (not (query-helper arg))]
         [(_ q) (has-tag-p q rec)]))
     (filter (lambda (r)
               (syntax-parameterize ([rec (syntax-id-rules () [_ r])])
                 (query-helper query)))
             records))))

Так менее устрашающе?

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

А то всё никак не мог понять, как запихнуть в define-syntax рекурсию.

В define-syntax тривиально:

(define-syntax my-or
  (syntax-rules ()
    [(_) #f]
    [(_ e) e]
    [(_ e1 e2 e3 ...)
     (let ([t e1])
       (if t t (my-or e2 e3 ...)))]))

а вот локальный макрос в правильное место воткнуть оказалось не очень тривиально.

monk ★★★★★ ()

Ещё я выделяю имена для локальных определений flet, labels, macrolet чтобы они не конфликтовали на случай определения имени для глобального окружения, т.е. переименовал бы query-helper на ~query-helper. Это для кода на CL, т.к. в схеме такой проблемы нет.

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

т.е. переименовал бы query-helper на ~query-helper

А смысл? В код раскрытия макроса это имя не попадает. Вот has-tag-p, or, and, not потенциально могут быть перекрыты.

Последние три из пакета CL, поэтому перекрывать их нельзя, а вот has-tag-p я бы защитил:

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

а вот локальный макрос в правильное место воткнуть оказалось не очень тривиально

Ну да, именно это. Так-то примеры простых рекурсивных define-syntax я видел.

Интересно, что первая моя попытка сделать это в CL была именно с локальными макросами, вложенными в select. Но там я застопорился.

А к тому времени, как я придумал окончательный вариант и стал думать, как сделать аналог на define-syntax, идея с локальными макросами уже была мною благополучно забыта.

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

А смысл? В код раскрытия макроса это имя не попадает.

CL системы очень динамичные системы - вполне возможно программист захочет ещё добавить определение query-helper в глобальном окружении. Тогда макрос перестанет работать.

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

У меня эта проблема и со flet не воспроизводится - это что получается, я сформировал свою привычку именования на основе уже исправленного косяка в sbcl?

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

А был ли косяк?

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

Вот сейчас подумал и понял, что защищать функцию можно и от случайного переопределения через defun

(let ((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)))
                     `(funcall ,f-has-tag-p ,q ,rec))))
        `(remove-if-not (lambda (,rec) ,(query-helper query)) ,records)))))

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

Сдаётся мне, что вот это вот (... (letrec-syntax ((query-helper — не R5RS.

По R5RS выходит, что <eliipsis> не может стоять в начале <template> (ну или я туплю):

7.1.5 Transformers
------------------

<transformer spec> -->
    (syntax-rules (<identifier>*) <syntax rule>*)
<syntax rule> --> (<pattern> <template>)
<pattern> --> <pattern identifier>
     | (<pattern>*)
     | (<pattern>+ . <pattern>)
     | (<pattern>* <pattern> <ellipsis>)
     | #(<pattern>*)
     | #(<pattern>* <pattern> <ellipsis>)
     | <pattern datum>
<pattern datum> --> <string>
     | <character>
     | <boolean>
     | <number>
<template> --> <pattern identifier>
     | (<template element>*)
     | (<template element>+ . <template>)
     | #(<template element>*)
     | <template datum>
<template element> --> <template>
     | <template> <ellipsis>
<template datum> --> <pattern datum>
<pattern identifier> --> <any identifier except '...'>
<ellipsis> --> <the identifier '...'>
aeralahthu ()
Ответ на: комментарий от aeralahthu

Мда. Действительно, появилось в R6RS: «A template of the form (<ellipsis> <template>) is identical to <template>, except that ellipses within the template have no special meaning. That is, any ellipses contained within <template> are treated as ordinary identifiers. In particular, the template (... ...) produces a single ellipsis, ....».

Тогда в R5RS только:

(define-syntax query-helper
  (syntax-rules (and or not)
    ((_ (and arg ...) rec) (and (query-helper arg rec) ...))
    ((_ (or arg ...) rec) (or (query-helper arg rec) ...))
    ;; исправил ошибку версии CL, там not к списку будет считаться нормальным
    ((_ (not arg) rec) (not (query-helper arg rec)))
    ((_ q rec) (has-tag-p q rec))))

(define-syntax select
  (syntax-rules ()
    ((select query records)
     (filter (lambda (rec) (query-helper query rec)) records))))))

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

Возможности в правилах писать _ вместо полного имени макроса в R5RS тоже не было, похоже. Но это уже мелочи.

А вложить определение query-helper в select нельзя, я так понимаю, из-за конфликта, кому интерпретировать ...?

Это получается, что в R5RS локальные макросы внутри макросов невозможны в принципе?

aeralahthu ()

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

У Chicken в define-syntax, помимо стандартного (по R5RS) декларативного syntax-rules, можно использовать процедурные трансформеры со встроенной гигиеной er-macro-transformer и ir-macro-transformer. «er» означает «explicit renaming», а «ir», соответственно, «implicit renaming». В первом случае нужно явно указывать, что мы хотим (в целях гигиены) переименовать, а во втором трансформер неявно переименовывает всё, и нужно отдельно указывать то, что мы переименовывать не хотим.

Вот мой макрос, реализованный с помощью ir-macro-transformer:

(define-syntax select
  (ir-macro-transformer
   (lambda (expr inject compare)
     (let ((query (cadr expr))
           (records (caddr expr)))
       (letrec ((query-helper
                 (lambda (q)
                   (if (list? q)
                       (cond ((compare (car q) (inject 'and))
                              `(and ,@(map query-helper (cdr q))))
                             ((compare (car q) (inject 'or))
                              `(or ,@(map query-helper (cdr q))))
                             ((compare (car q) (inject 'not))
                              `(not ,(query-helper (cadr q))))
                             (else `(has-tag? ,q rec)))
                       `(has-tag? ,q rec)))))
         `(filter (lambda (rec) ,(query-helper query)) ,records))))))

Хотел было описать, как это работает, но, попытавшись, осознал, что я и сам не настолько хорошо это понимаю.

Если коротко, моя функция-трансформер получает на вход три параметра (я их назвал expr, inject и compare): список аргументов макроса, функцию для переименования (Вернее, в данном случае, для отмены переименования? Тут моё понимание начинает плыть.) и функцию для сравнения переименованных сущностей.

Вот тут видно, как работает переименование:

#;328> (pp (expand '(select (and tag1 (not tag2) (or tag3 (not tag4))) my-records)))
(filter857
  (lambda858
    (rec859)
    (and849
      (has-tag?860 tag1 rec859)
      (not851 (has-tag?860 tag2 rec859))
      (or853 (has-tag?860 tag3 rec859) (not851 (has-tag?860 tag4 rec859)))))
  my-records)
aeralahthu ()
Ответ на: комментарий от aeralahthu

Это как syntax-case, только велосипедно. Вариант с syntax-case нужен? Могу написать.

(and849 ... (has-tag?860

Он точно эти идентификаторы взял из окружающего контекста, в смысле «and849 = and» и «has-tag?860 = has-tag?»?

Макрос работает?

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

Вариант с syntax-case нужен? Могу написать.

Да, если не трудно. Интересно будет сравнить.

Он точно эти идентификаторы взял из окружающего контекста, в смысле «and849 = and» и «has-tag?860 = has-tag?»?

Насколько я понимаю, да.

Макрос работает?

Работает:

#;169> (pp (let ((x 'scheme)) (select (and (or x 'lisp) (not 'book) 'people) bms)))
(((lisp programming blog people)
  "http://www.paulgraham.com/"
  "Paul Graham")
 ((scheme programming people scheme)
  "http://www.neilvandyke.org/"
  "Neil Van Dyke's (im)personal site")
 ((scheme programming courses people)
  "https://groups.csail.mit.edu/mac/users/gjs/"
  "Gerald Jay Sussman"))
aeralahthu ()
Ответ на: комментарий от aeralahthu
(define-syntax select
  (lambda (stx)
    (syntax-case stx ()
      [(select query records)
       (letrec ((query-helper (lambda (q)
                                (syntax-case q (and or not)
                                  [(and qq ...) #`(and #,@(map query-helper #'(qq ...)))]
                                  [(or qq ...) #`(or #,@(map query-helper #'(qq ...)))]
                                  [(not qq) #`(not #,(query-helper #'qq))]
                                  [_ #`(has-tag? #,q rec)]))))
         #`(filter (lambda (rec) #,(query-helper #'query)) records))])))
monk ★★★★★ ()
Ответ на: комментарий от monk

Ага, спасибо.

Это как syntax-case, только велосипедно.

Всё-таки, как я вижу, не совсем. syntax-case — отдельный декларативный язык шаблонов. А Chicken’овские трансформеры — всего лишь гигиенизирующая надстройка над процедурной трансформацией синтаксиса в стиле CL.

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

А Chicken’овские трансформеры — всего лишь гигиенизирующая надстройка над процедурной трансформацией синтаксиса в стиле CL.

Так короче, но можно и просто как надстройку использовать:

(define-syntax select
  (lambda (stx)
    (syntax-case stx ()
      [(select query records)
       (letrec ((query-helper
                 (lambda (q)
                   (if (list? q)
                       (cond ((eq? (car q) 'and)
                              `(and ,@(map query-helper (cdr q))))
                             ((eq? (car q) 'or)
                              `(or ,@(map query-helper (cdr q))))
                             ((eq? (car q) 'not)
                              `(not ,(query-helper (cadr q))))
                             (else `(has-tag? ,q rec)))
                       `(has-tag? ,q rec)))))
         #`(filter (lambda (rec) #,(datum->syntax #'rec (query-helper (syntax->datum #'query)))) records))])))

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

И можно без letrec:

(define-syntax select
  (lambda (stx)
    (define query-helper
      (lambda (q)
        (if (list? q)
            (cond ((eq? (car q) 'and)
                   `(and ,@(map query-helper (cdr q))))
                  ((eq? (car q) 'or)
                   `(or ,@(map query-helper (cdr q))))
                  ((eq? (car q) 'not)
                   `(not ,(query-helper (cadr q))))
                  (else `(has-tag? ,q rec)))
            `(has-tag? ,q rec))))
    (syntax-case stx ()
      [(select query records)
       #`(filter (lambda (rec) #,(datum->syntax #'rec (query-helper (syntax->datum #'query)))) records)])))

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

Эврика /шутка/!

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

Владимир

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

Там есть ещё такой синтаксис (SRFI-110)

define-syntax select(stx)
  define query-helper(q)
    if list?(q)
       cond 
         {car(q) eq? 'and}
           `and $ ,@map $ query-helper cdr(q)
         {car(q) eq? 'or}
           `or $ ,@map $ query-helper cdr(q)
         {car(q) eq? 'not}
           `not $ ,query-helper cadr(q)
         else `has-tag? ,q rec
    syntax-case stx ()
      select query records
      #`filter 
          lambda(rec) 
            #,datum->syntax
              #'rec 
              query-helper syntax->datum(#'query)
          records
monk ★★★★★ ()
Ответ на: комментарий от monk

Есть ещё SRFI-49 и SRFI-119 на ту же тему.

Вот только, мне кажется, все эти бесскобочные синтаксисы для лиспа изобретают по двум причинам: либо от отсутствия сколько-нибудь серьёзного опыта работы на лиспе, либо как бесполезный, но забавный proof of concept (чтобы было чем ответить на комментарии вроде этого).

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

а вот has-tag-p я бы защитил

Защитил от того чтобы при переопределении функции всё продолжало использовать актуальный код?

Вообще, у меня ощущение, что оборонительное программирование в CLс одной стороны почти бесполезно (очень-очень трудоёмко), а с другой стороны вырабатывает привычки «на всякий случай»

«На всякий случай» делают только неосиляторы. Видел недавно библиотечку, пришлось чтобы сделать её юзабельной выпилить все «на всякий случай». И код стал короче на 10% и понятнее на 40%.

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

Защитил от того чтобы при переопределении функции всё продолжало использовать актуальный код?

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

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

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

Получается, что на CL надо писать либо в одиночку, либо на условиях «без гарантий, используйте как хотите». Ведь любой разработчик может (иногда ненамеренно) испортить код в любом пакете проекта.

Видел недавно библиотечку, пришлось чтобы сделать её юзабельной выпилить все «на всякий случай». И код стал короче на 10% и понятнее на 40%.

Убрал проверки?

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

а вот has-tag-p я бы защитил

Защитил от того чтобы при переопределении функции всё продолжало использовать актуальный код?

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

В правильно написанном макросе невозможно случайное переопределение функций.

Получается, что на CL надо писать либо в одиночку, либо на условиях «без гарантий, используйте как хотите». Ведь любой разработчик может (иногда ненамеренно) испортить код в любом пакете проекта.

Можно писать коллективом. Только в коллективе не должно быть безответственных и откровенных идиотов.

Убрал проверки?

Я думаю он убрал ненужные проверки.

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

Так трудно просто ставить приставку ~ у определений с flet, labels? Какие-то мелочи, а приговариваешь к растрелу. В ракете вон https://docs.racket-lang.org/style/Textual_Matters.html#(part._names) - целая таблица общепринятых иероглифов помимо вполне человеческих ? и !, хотя и с конфликтами определений в макросах проблем нет и пространства имён круче.

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

В правильно написанном макросе невозможно случайное переопределение функций.

Покажи, как бы ты правильно написал этот макрос, если has-tag-p и select экспортируются.

Только в коллективе не должно быть безответственных и откровенных идиотов.

Ну да. Кто-то уже писал: язык для инженеров. Для крупного проекта сложно найти столько не-идиотов. Часто приходится работать с теми, кто есть. И в этом случае Scheme лучше, чем CL, а C# лучше, чем С++.

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

Так трудно просто ставить приставку ~ у определений с flet, labels?

А можно тут поподробнее? Нужно в имени каждой локальной функции использовать специальный префикс? Или только в каких-то случаях?

Может есть где-то готовый список правил, позволяющий в CL избежать потенциальных ошибок?

В ракете вон - целая таблица общепринятых иероглифов

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

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

Так трудно просто ставить приставку ~ у определений с flet, labels?

Так has-tag-p публичная, а не локальная. С локальными там для макросов всё замечательно - есть gensym.

Какие-то мелочи, а приговариваешь к растрелу.

Если человек намеренно переопределяет библиотечную функцию, в которую раскрывается макрос, чтобы изменить поведение макроса, а не пишет свою версию макроса, то это не мелочь. Разве что он пишет в одиночку и его программа не может использоваться как часть другой программы. Это всё равно, что в C++ в своём модуле переписать VTable, чтобы метод базового класса делал что-нибудь другое.

целая таблица общепринятых иероглифов помимо вполне человеческих ? и !

Это исключительно для читабельности.

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

Только в коллективе не должно быть безответственных и откровенных идиотов.

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

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

А можно тут поподробнее? Нужно в имени каждой локальной функции использовать специальный префикс? Или только в каких-то случаях?

Есть разные стили. Кто-то ставит префикс для всех локальных переменных и функций. Кто-то только для локальных функций. В принципе, по крайней мере для функций крайне рекомендуется, если внутри локального блока используются чужие макросы. Потому что если кто-то напишет (flet ((has-tag-p ...)) (select ...)), то select может поломаться (если только в нём явно в документации не указано, что выборка должна делать через has-tag-p). Поэтому, в общем случае пишут (flet ((~has-tag-p ...)) ...) или (flet ((%has-tag-p ...)) ...).

Префикс также позволяет использовать имена, занятые пакетом CL. (flet ((~set ...) (~do ...) (~loop ...)) ...).

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

Спасибо за разъясняющие примеры.

Правильно ли я понимаю, что в Scheme все эти ухищрения не нужны? (Если, конечно, не пользоваться какими-нибудь негигиеничными расширениями, вроде define-macro в Guile.)

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

Правильно ли я понимаю, что в Scheme все эти ухищрения не нужны?

Да. В Racket я могу спокойно сделать (не знаю, можно ли так в любой Scheme)

(define (list ship)
  ;; в этом модуле я не создаю списки через функцию list
  ;; поэтому могу получить крен судна
  (/ (delta ship) (height ship)))

В произвольной Scheme можно что-то вроде

(define my-map 
  (lambda (f list)
    (if (null? list) 
        null
        (cons (f (car list)) (cdr list)))))
Внутри функции я могу использовать любые имена, если внутри этой функции не используется одноимённый библиотечный символ.

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

Покажи, как бы ты правильно написал этот макрос, если has-tag-p и select экспортируются.

  1. Зачем экспортировать has-tag-p ?
  2. Если все таки надо, воспользоваться доработанным once-only
  3. Ты сам догадался, но неправильно обернул в лет. А еще лучше избавиться от функола (используя fdefinition или symbol-function), особенно если если надо использовать более одного раза.
    (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))))
    

Ну да. Кто-то уже писал: язык для инженеров. Для крупного проекта сложно найти столько не-идиотов. Часто приходится работать с теми, кто есть. И в этом случае Scheme лучше, чем CL, а C# лучше, чем С++.

Это я писал)

Во время разработки много народу не нужно, а для поддержки Scheme не лучше чем ЦЛ, для этого придумали Java и C++ - можно набрать много не особо квалифицированных подмастерьев.

anonymous ()
Ответ на: комментарий от anonymous
(let ((rec (gensym "RECORD"))
        (f-has-tag-p (gensym)))
    (setf (fdefinition f-has-tag-p) #'has-tag-p)

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

(defun has-tag-p ...)

(.... (select ....) ...)

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

monk ★★★★★ ()