LINUX.ORG.RU

Я верно понимаю, что в scheme нельзя макрос выполнять изнутри макроса?

 ,


1

2

На лиспе есть вот такой код

(defmacro template (vars args &body body)
  (let ((%do (gensym "DO")) 
        (%vars (gensym "VARS")))
    (cond
      ((null vars)
       `(macrolet ((,%do () ,@body))
          (,%do)))
      ((consp vars)
       `(template ,%vars ,args
          (destructuring-bind ,vars ,%vars
            ,@body)))
      (t `(macrolet ((,%do ()
                       `(progn
                          ,@(mapcar (lambda (,vars) ,@body)
                                    ',args))))
            (,%do))))))

Используется, например, так:

(template 
  ((type word) (list :in) (string :across) (vector :across))
  `(defmethod process ((l ,type))
     (loop :for i ,word l :collect (process-element i)))

Суть макроса template в том, что создаётся анонимный макрос по переданному коду и тут же выполняется. Можно ли такое сделать в Scheme или Racket?

★★★★★

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

Можно. Читай мануал. /thread

anonymous
()

Можно ли такое сделать в Scheme или Racket?

Можно, т.к. макросистема CL вляется строгим подмножеством. Пример экспанда приведи, а то обфусцированные общемакросы нереально распарсить.

anonymous
()

У вас походу в примере ошибка. Я так понял: есть список vars, есть список args со списками возможных значений этих vars. Так вот в примере почему то все одним списком. Ведь (type word) - это vars? Как я понял - вам надо сгенерировать кучу однотипных определений?

Пример:

(define-syntax-rule (define-many (id val) ...)
  (begin
    (define id val)
    ...))

(define-many (x 1) (y 2) (z 3))

qaqa ★★
()

Например:

(define-syntax with-template
  (syntax-rules ()
    ((_ ((var ...) (form ...) ...)
        body
        ...)
     (begin
       (define-syntax %inner-macro
         (syntax-rules ()
           ((_ var ...)
            (begin
              body
              ...))))
       (%inner-macro form ...)
       ...))))
#lang swindle

(defgeneric (process function collection))

(with-template
    (( type)
     (<list>)
     (<vector>))
  (defmethod (process fn (c type))
    (list-of (fn x) [x <- c])))

; Добро пожаловать в DrRacket, версия 5.3 [3m].
; Язык: swindle; memory limit: 512 MB.
; > (process add1 '(1 2 3))
; '(2 3 4)
; > (process add1 #(1 2 3))
; '(2 3 4)
; > (process add1 "123")
; . . process: no applicable primary methods for arguments (#<procedure:add1> "123"), of types (#<procedure-class:primitive-procedure> #<primitive-class:immutable-string>)
; >
#lang racket

(with-template
    ((name           in-collection)
     (process-list   in-list)
     (process-vector in-vector))
  (define (name process collection)
    (for/list ((item (in-collection collection)))
      (process item))))

; Добро пожаловать в DrRacket, версия 5.3 [3m].
; Язык: racket; memory limit: 512 MB.
; > (process-list add1 '(1 2 3))
; '(2 3 4)
; > (process-vector add1 #(1 2 3))
; '(2 3 4)
; > 
korvin_ ★★★★★
()
Ответ на: комментарий от korvin_

Да. Это то, что я хотел.

В заблуждение меня ввела фраза из http://docs.racket-lang.org/compatibility/defmacro.html

«it is still restricted by Racket’s phase separation rules. This means that a macro cannot access run-time bindings, because it is executed in the syntax-expansion phase.»

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

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

Если под racket, то лучше записать в таком виде:

(define-syntax-rule (with-template ([var ...] [form ...] ...) body ...)
  (begin (define-syntax-rule (inner var ...) (begin body ...))
         (inner form ...) ...))

«it is still restricted by Racket’s phase separation rules. This means that a macro cannot access run-time bindings, because it is executed in the syntax-expansion phase.»

Имеется ввиду следующее:

Добро пожаловать в DrRacket, версия 5.3.1 [3m].
Язык: racket [выбранный].
> (define x 1)
> (define-syntax (yoba stx) #`#,x)
> (yoba)
x: undefined;
 cannot reference an identifier before its definition
  phase: 1
  explanation: cannot access the run-time definition
> (define-for-syntax x 1)
> (yoba)
1
> 

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

Я из этого (неверно) понял, что нельзя внутри define-syntax сделать ещё раз define-syntax и сразу выполнить.

У меня и не делается define-syntax внутри define-syntax. У меня первый макрос генерирует код, в котором определяется второй. Так же как и в твоем CL-коде.

Та фраза означает, что ты не можешь сделать так:

(define (transpose vars vals)
  (map list vars vals))

(defmacro foos (vars vals)
  (let ((binds (transpose vars vals)))
    `(quote ,binds)))

(foos (x y z) (1 2 3))

; . . ..\..\Program Files\Racket\collects\racket\private\modbeg.rkt:46:4: transpose: undefined;
;  cannot reference an identifier before its definition
;   phase: 1
;   explanation: cannot access the run-time definition

Нужно определять transpose через define-for-syntax, а не define. Соответственно, если хочешь чтобы функция была доступна и во время раскрытия и во время компиляции/выполнения, нужно определить ее дважды (или сделать макрос, определяющий ее для обоих фаз).

теперь буду пытаться понять, как оно работает без цикла по переменным

Паттерн-матчинг же.

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

Нужно определять transpose через define-for-syntax, а не define.

Ну это и в CL почти так: или eval-when или обязано быть в разных файлах.

Паттерн-матчинг же.

Он круче, чем мне раньше казалось... :-)

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

Ну это и в CL почти так: или eval-when или обязано быть в разных файлах.

Нет, не так. В CL фаза одна единственная (нулевая, рантайм) а eval-when указывает лишь этап исполнения. for-syntax (template/meta/etc) указывают фазу _внутри_ которой будет исполнена форма. Не _во время_, а _внутри_. Каждая фаза - это по сути отдельная лисп-машина с отдельным образом, независимым от остальных (если совсем условно говорить). .

Он круче, чем мне раньше казалось... :-)

Это обычный схемовский паттерн-матчинг. В racket есть еще syntax-parse - он на порядок мощнее.

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

Ну это и в CL почти так: или eval-when или обязано быть в разных файлах.

Эм... не обязано, только что в LispWorks проверил.

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

И я не знаю, зачем ты такую кашу замутил с CL. Проще же надо:

(defmacro template (vars args &body body)
  (let* ((macroname (gensym "MACRO-"))
         (forms (loop :for vals :in args
                      :collect (cons macroname vals))))
    `(macrolet ((,macroname ,vars ,@body))
       ,@forms)))

Или я чего-то не понимаю?

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

Эм... не обязано, только что в LispWorks проверил.

Ты через compile-file проверь. Через load естественно работает.

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

Ты на пост выше обрати внимание. Тут дело не в том когда выполняется форма а в каком окружении она выполняется.

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

Или я чего-то не понимаю?

Согласен. Макросы я довожу до состояния «верно работает», а дальше до рефакторинга дело не доходит. Только в твоём варианте (template () () (generate-code)) не работает.

Полный эквивалент

(defmacro template (vars args &body body)
    (let* ((macroname (gensym "MACRO-"))
           (forms (loop :for vals :in args
                        :collect (cons macroname vals))))
      `(macrolet ((,macroname ,vars ,@body))
         ,@(or forms (list (list macroname))))))

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

Тут дело не в том когда выполняется форма а в каком окружении она выполняется.

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

А как показывать окружение: for-syntax или eval-when — несущественно.

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

Ты через compile-file проверь. Через load естественно работает.

Через compile-file и делаю.

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

В любом случае нельзя класть их в один файл

Кстати, хотелось бы ссылку на соответствующий текст в стандарте.

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

А как показывать окружение: for-syntax или eval-when — несущественно.

Существенно. В общелиспе оно в разных файлах работает, а в Racket - и в разных не работает. Вообще никак не работает. eval-when - выполнить в _текущем_ окружении, for-syntax - выполнить _в другом_ окружении.система модулей и вообще семантика экспанда существенно завязана на фазы.

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

В стандарте нашёл только обязательность заворачивания defmacro при использовании в том же файле.

file:///usr/share/doc/hyperspec/Body/03_bcaa.htm

Виноват, что-то напутал.

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

file:///usr/share/doc/hyperspec/Body/03_bcaa.htm

file:///C:/Temp/facepalm.jpg =)

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