LINUX.ORG.RU

Вопрос по макросам

 


1

2

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



Последнее исправление: terminator-101 (всего исправлений: 2)
Ответ на: комментарий от anonymous

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

$ sbcl
* (compile-file "test.lisp")

$ sbcl 
;  здесь стейт уже не засран
* (load "test")
; запустили только то, что должно запускаться в runtime

Полная принудительная автоматизация такого подхода (в стиле Racket) реализована в библиотеке xcvb.

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

И нам нельзя получить «копию» модуля в рантайме

Теперь понял. Ровно это делает xcvb. Компиляция запускается в отдельном процессе => сторонние эффекты на текущий образ не влияют.

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

(begin-for-syntax (d))

Что ещё непривычней для Common Lisp'а — после компиляции модуля все эффекты сбрасываются. То есть, например, хочешь посчитать количество вызовов макроса, делаешь (begin (add) #'(...)), после компиляции запускаешь (d), а он тебе в ответ: 0. Очень удивляет, когда впервые натыкаешься.

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

Я это и имею в виду. Один и тот же код в REPL или через load работает, а если его же завернуть в модуль, то уже не работает.

Один код работает, другой код - не работает. Я не понимаю, чему ты удивляешься? Это разный код, он ведет себя по-разному.

Но твой пример, на самом деле, к обсуждаемому вопрос не относится вообще - там евал не срабатывает не изза фаз/модулей и т.п., а из-за того, что current-namespace внутри модуля не меняется на неймспейс этого модуля. В принципе, совершенно ничего не мешало менять.

так как в первом случае будет #%plain-module-begin, а во втором #%module-begin

#%module-begin - это и есть #%plain-module-begin, просто он экспортируется kernel с укороченным именем. Так что нет никакой заморочки.

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

Теперь понял. Ровно это делает xcvb. Компиляция запускается в отдельном процессе => сторонние эффекты на текущий образ не влияют.

А если все-таки захочется что-то из компиляции протащить? Кроме того, в ракетке есть такая штука как cross-phase persistent module

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

Что ещё непривычней для Common Lisp'а — после компиляции модуля все эффекты сбрасываются.

Нет, не сбрасываются.

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

#%module-begin - это и есть #%plain-module-begin

Если разработчик языка переопределил #%module-begin (а это часто требуется), то появляются сюрпризы.

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

Нет, не сбрасываются.

#lang racket

(begin-for-syntax
  (define n 0)
  (define (add) (set! n (add1 n)))
  (define (show) (displayln n)))

(define-syntax (count stx)
  (syntax-case stx ()
    [(_ body) (begin (add) #'body)]))
    
(count 1)
(count 2)

;; REPL
1
2
> (begin-for-syntax (show))
0 ;; а должно быть 2, ведь count вызывался дважды
> 
monk ★★★★★
()
Ответ на: комментарий от anonymous

А если все-таки захочется что-то из компиляции протащить?

Так eval-when ведь позволяет протащить что угодно.

Кроме того, в ракетке есть такая штука как cross-phase persistent module

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

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

Так eval-when ведь позволяет протащить что угодно.

Ну так у нас же xcvb форсит компиляцию в отдеьном процессе?

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

В нем можно делать то, зачем они нужны :)

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

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

Ну так у нас же xcvb форсит компиляцию в отдеьном процессе

Можешь тогда привести пример (через тот же persistent модуль, например), что понимаешь под словом «протащить».

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

(defmacro (to-runtime)
   '(defvar *compile-data* ,*complex-compile-time-structure*)))

(to-runtime)

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

надо так

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

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

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

Нет, не уходят:

#lang racket
(define-for-syntax n 0)

(define-syntax (yoba stx)
  #'(begin-for-syntax (set! n (add1 n))))

(yoba)
(yoba)
->
> (begin-for-syntax (displayln n))
2
> 

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

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

Дык оно же в отдельном процессе?

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

надо так:

Кстати так вообще не работает:

(begin-for-syntax
  (define n 0)
  (define (add) (set! n (add1 n))))

(define-syntax (count stx)
  (syntax-case stx ()
    [(_ body) #'(begin (begin-for-syntax (add)) body)]))

(define (foo x)
  (count (+ x 1)))

ошибку даёт.

Мой вариант хоть ответ правильный даёт:

(begin-for-syntax
  (define n 0)
  (define (add) (set! n (add1 n)))
  (define (show) (displayln n)))

(define-syntax (count stx)
  (syntax-case stx ()
    [(_ body) (begin (add) #'body)]))

(define (foo x)
  (count (+ x 1)))

(define-syntax (show stx)
  #`#,n)

(define (at-end)
  (show))

> (at-end)
1

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

Дык оно же в отдельном процессе?

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

(defmacro (to-runtime)
   '(defvar *compile-data* ,*complex-compile-time-structure*)))

(to-runtime)

То есть

(defvar *compile-data* {здесь константное значение полученное при компиляции})

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

Нет, не уходят:

Здесь (begin-for-syntax (set! n (add1 n)) — скомпилированный текст программы, а не действия при компиляции.

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

(begin-for-syntax (set! n (add1 n)) - это именно действия при компиляции

Делаем проверку. Запишем в testmod.rkt

#lang racket
(define-for-syntax n 0)

(define-syntax (yoba stx)
  #'(begin-for-syntax (displayln "Compiling") (set! n (add1 n))))

(yoba)
(yoba)

Если ты прав, то Compiling будет только при компиляции. Теперь делаем модуль с одной строкой (require «testmod.rkt»).

Внезапно

Compiling
Compiling
Compiling
Compiling
Compiling
Compiling
> 

Причём, если эта картина не меняется, даже если testmod.rkt не перекомпилируется (смотрю по .zo). Кстати, а с чего он трижды выполняется?

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

Если ты прав, то Compiling будет только при компиляции.

Именно так.

Внезапно

А не надо в dr.racket запускать :)

F:\Users\*>raco make yoba.rkt
Compiling
Compiling

F:\Users\*>racket yoba.rkt

F:\Users\*>
anonymous
()
Ответ на: комментарий от anonymous

А не надо в dr.racket запускать :)

И кто бы после этого кидал камни в огород Common Lisp!? Запустили в DrRacket — одна программа, с командной строки — другая... Да, через raco make всё предсказуемо и однозначно.

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

А зачем может понадобиться вручную удалять .zo?

Мне приходилось когда при запуске он мне рассказывал что-то про то, что не может найти мою переменную в фазе 0. При том, что она была в модуле. Изменения модуля (которые по-идее должны приводить к перекомпиляции) не помогали. Удалил *.zo — всё замечательно перекомпилировалось. К сожалению, воспроизвести ситуацию не удалось.

P.S. Да, компилировал в DrRacket

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

И кто бы после этого кидал камни в огород Common Lisp!? Запустили в DrRacket — одна программа

Как и что выполняется в иде - зависит исключительно от этой иде, а не от языка.

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

Изменения модуля (которые по-идее должны приводить к перекомпиляции) не помогали. Удалил *.zo — всё замечательно перекомпилировалось. К сожалению, воспроизвести ситуацию не удалось.

Ну это какой-то баг, скорее всего. Собственно, запускаешь raco make - и компилируешь, удалять не надо ничего.

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

Ещё одна комбинация семантики (не как в DrRacket и не как с командной строки). Сначала выполнение, потом компиляция :)))

Дык у самописных «запускалок» семантика такая, которую ты напишешь, что тут странного? Язык и его библиотека с вм не могут овтечать за то, как их запустит какой-то third-party soft. Главное чтобы семантика была четкая и однозначная для стандартных средств - и оно в ракетке есть, в отличии от общелиспа.

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

Как и что выполняется в иде - зависит исключительно от этой иде, а не от языка.

Вот! В случае CL тоже если его использовать единообразно (через asdf или xcvb) то и семантика будет единообразной и предсказуемой. А если пользоваться тем, что можно запускать не компилируя, просто скомпилировать, запустить с компилированием, ... так в Racket тоже методами запуска можно фигню получить.

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

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

http://pasterack.org/pastes/92364

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

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

Вот! В случае CL тоже если его использовать единообразно (через asdf или xcvb) то и семантика будет единообразной и предсказуемой. А если пользоваться тем, что можно запускать не компилируя, просто скомпилировать, запустить с компилированием, ... так в Racket тоже методами запуска можно фигню получить.

Ну я все-таки не уверен, что там все хорошо на счет xcvb. В asdf все плохо, это понятно :)

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

Так и без этого можно же.

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

Так и без этого можно же.

Фазы слегка мешают. Вот как мне в примере с (define-for-syntax n 0) получить этот n в окошке GUI (состояние на окончание компиляции)?

В смысле

(define frame (new frame% [label ""]))
(define label (new message% [parent frame] [label {значение n по окончании компиляции}]))

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

Фазы слегка мешают. Вот как мне в примере с (define-for-syntax n 0) получить этот n в окошке GUI (состояние на окончание компиляции)?

(provide n)
(define-syntax (get-n stx) #`#,n)
(define n (get-n))

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

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

(define-syntax (get-n stx) #`#,n)

Это работает только если все экспанды в топлевеле. Вот здесь: Вопрос по макросам (комментарий) приходится завернуть в функцию и вызов функции помещать обязательно в конец файла. Ну и не работает #'(begin (begin-for-syntax (add)) body), так как выдаёт ошибку при запуске из функции.

А в CL

(defmacro count (&body body)
   (incf *n*)
   body)

Работает в любом контексте и для получения ответа достаточно выполнить (compile-file «test.lisp») (print test:*n*)

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

Это работает только если все экспанды в топлевеле.

Ну да. Тебе никто не гарантирует (и не может, это неразрешимо), что в require-модуле будут раскрыты все макросы до того, как начнут исполняться эффекты enclosed модуля. Гарантируется только, что эффекты require-модуля будут исполнены до эффектов enclosed. По-этому ты получаешь единственный возможный консистентный результат - учитывающий эффекты, но не учитывающий экспанд.

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

А в CL

В CL эффекты внутри макросов считаются компайлтайм эффектами (что сразу лишает систему овзможности быть консистентной). В оакетке эффекты - это только то, что в begin-for-syntax (которое на самом деле лишь define-syntax и считается эффектами постольку, поскольку define-syntax создает рантайм биндинг и его _нельзя_ не исполнять в рантайме, даже если у тебя все скомпилировано)

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

В CL эффекты внутри макросов считаются компайлтайм эффектами

В Racket тоже. Только в нем эти эффекты отменяются после компиляции.

Во время компиляции модуля я вполне могу хранить промежуточные данные в переменных, а не в #'(begin-for-syntax ...). Только не могу увидеть эти данные после окончания компиляции. http://docs.racket-lang.org/reference/eval-model.html#(part._separate-compila...

Тебе никто не гарантирует (и не может, это неразрешимо), что в require-модуле будут раскрыты все макросы до того, как начнут исполняться эффекты enclosed модуля.

Я не про модули, а про тела функций. В них #'(begin-for-syntax ) не работает

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

В Racket тоже.

Нет.

Только в нем эти эффекты отменяются после компиляции.

Они вообще не совершаются. Если я скомпилированный фаел запущу for-syntax, то ни один из них не будет исполнен.

Во время компиляции модуля я вполне могу хранить промежуточные данные в переменных, а не в #'(begin-for-syntax ...). Только не могу увидеть эти данные после окончания компиляции.

Если вам эффекты компиляции не нужны, то тогда так и пишите:

(provide n)
(define-for-syntax n 0)
(define-syntax (inc-n stx) *тут set!*)
(define-syntax (get-n stx) #`#,n)
(define n (get-n))

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

В них #'(begin-for-syntax ) не работает

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

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

Если я скомпилированный фаел запущу for-syntax, то ни один из них не будет исполнен.

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

Только не могу увидеть эти данные после окончания компиляции.

Если вам эффекты компиляции не нужны, то тогда так и пишите:

Так задачу «увидеть состояние на момент окончания компиляции» это решить не помогает. Разве что в файл записать :-)

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

Так задачу «увидеть состояние на момент окончания компиляции» это решить не помогает. Разве что в файл записать :-)

А, я думал надо в рантайме в окошке рисовать. Ну исправить так тогда:

(define-syntax (get-n stx) (displayln n) #`#,n)

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

А, я думал надо в рантайме в окошке рисовать.

Так после компиляции ведь рантайм. Так что «в окошке».

Проблема в другом.

(define (foo x)
  (inc-n)
  ...)

(define label (new message% [parent frame] [label (get-n)])

(define (bar x)
  (inc-n)
  ...)

Есть предположение, что (get-n) выдаст n не на момент окончания компиляции

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

Есть предположение, что (get-n) выдаст n не на момент окончания компиляции

У тебя компилируется модуль «yoba.rkt», делаешь в другом модуле (require «yoba.rkt») и там уже (define label (new message% [parent frame] [label n])

get-n нинужно.

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

yoba.rkt

#lang racket
(provide n)
(define-for-syntax n 0)
(define-syntax (inc-n stx) (set! n (add1 n)) #'(void))
(define-syntax (get-n stx) #`#,n)
(define n (get-n))

(define (foo x)
  (inc-n)
  x)

(define (bar x)
  (inc-n)
  (+ x 1))

test.rkt:

#lang racket
(require "yoba.rkt")
(displayln n)

Выводит 0. А должен 2.

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

(define n (get-n)) вниз перенеси

Вот и я про то же. Нужен отдельный модуль, дополнительный define-syntax, не забыть «сохранить» данные последней строкой. В CL это проще. Также как cl:defmacro проще отлаживать, чем define-syntax

С другой стороны в CL невозможно то, что возможно с define-syntax и раздельными фазами :-)

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

Вот и я про то же. Нужен отдельный модуль, дополнительный define-syntax, не забыть «сохранить» данные последней строкой. В CL это проще. Также как cl:defmacro проще отлаживать, чем define-syntax

#lang racket
(require syntax/parse/define
         (for-syntax racket
                     racket/syntax))

(define-simple-macro (define-compile-variable id:id e:expr)
  #:do [(syntax-local-lift-module-end-declaration #'(define id (get-id)))]
  (begin (begin-for-syntax (define id e))
         (define-syntax (get-id stx) (displayln id) #`#,id)))

(define-compile-variable n 0)

(define-syntax (yoba stx)
  (set! n (add1 n))
  #'(void))

(yoba)
(yoba)
(let ()
  (yoba)
  (yoba))

По части простоты отладки макросов - учитывая наличие в ракетке макростепера, тут общелиспу как до Луны, если не дальше.

Алсо, в общелиспе без отдельного модуля тоже нельзя же. Ну и, кстати, мой вариант решения гарантирует, что вне зависимости от запуску и от факта компиляции n = 4. А в CL значение n будет зависеть от того что и как запускал. Про многократный запуск одного и того же модуля уже молчу.

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

По части простоты отладки макросов - учитывая наличие в ракетке макростепера, тут общелиспу как до Луны, если не дальше

макростепера — который в DrRacket? В более-менее сложной программе он стремится раскрывать всё что угодно, кроме того, что нужно. Или я его не понимаю

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

макростепера — который в DrRacket? В более-менее сложной программе он стремится раскрывать всё что угодно, кроме того, что нужно.

Его вообще-то можно вызывать руками на нужный терм, я давным-давно себе сделал костылик для гейзера, который дергает expand/step вместо обычного экспанда. Ну а дальше hide-show кнопочки внизу уже вполне могут быть юзабельны. Что печально - это то что нельзя делать свои наборы правил.

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

Что печально - это то что нельзя делать свои наборы правил.

Обнаружил expand/step-text. Там есть второй параметр show? в который можно передавать функцию-фильтр.

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

Вот если б оно еще не текстом было, а как в макростепере - с возможностью туда-сюда по шагам открывать/раскрывать, цветами связанные биндинги выделяя, возможностью посмотреть св-ва идентификатора/syntax object'a и т.п. :(

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

Вот если б оно еще не текстом было, а как в макростепере

Да, нет в жизни счастья.

Можно отдельно «как в макростепере» через browse-syntax + expand/show-predicate. Но без шагов.

Можно с шагами через stepper-text, но текстом.

Ещё можно открыть исходники stepper-text и заменить в них show-step на версию с browse-syntaxes. :-)

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