LINUX.ORG.RU

Откуда у Scheme такие макросы?

 ,


1

2

Существует ли книга, в которой написано, почему макросы в scheme такие странные.

Логика макросов CL понятна. Всё есть список, дальше программист сам разберётся.

Часть решений в Scheme тоже: гигиена по-умолчанию, паттерны.

Но зачем нужна отдельная от списка структура кода (syntax)? Почему этот syntax, будучи созданным внутри макроса не привязывается автоматически к строке, где был использован макрос (вместо #' #` приходится писать syntax/loc)?

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

Как-то так:

(use-from (racket/contract)
  ;; здесь видны символы текущего модуля и racket/contract
  (provide/contract [my-func (string? . -> . string?)]))
syntax:
(use-from (req-expr ...) expr ...)

req-expr == всё, что может быть в (require ...)

NB! текущий модуль не меняется. Если написать provide или
require, они влияют на текущий модуль
monk ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Один макрос раскрывается в кейворд одного модуля, другой - в такгй же кеворд другого модудя

Как раз для этого в CL придумали пакеты. Эффект тот же, но делать надо вручную.

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

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

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

А мы не всегда можем знать имя текузего пакета, соответственно и квалификатор подставить не сможем

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

Ну можно враппер вокруг вложенных модулей сделать.

Как при этом require и provide от текущего модуля делать, а не от вложенного (просто в 90% случаев эта конструкция нужна вокруг provide).

Помоги ещё с одним примером:

(module def+ racket
  (provide define+ (for-syntax names))
  (define-for-syntax names null)

  (define-syntax (define+ stx)
    (syntax-case stx ()
      [(_ name body ...)
       (set! names (cons #'name names))
       (syntax/loc stx (define name body ...))])))

(require (for-template (only-in 'def+ names)) 'def+)

(define+ (foo) 1)
(define+ bar 2)
;;; запускаю
> names
'()
> (define+ zzz 3)
> names
'()

Почему переменная не меняется? Хотел сделать (в терминах CL):

(defmacro define+ (name &body body)
  (push name *names*)
  `(define ,name ,@body))

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

А мы не всегда можем знать имя текузего пакета

Это как? В CL переменная *package* = текущий пакет. Символ вне пакета быть не может (по построению).

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

Теоретически такое может быть только если символы (кейворды) сравнивать по миени, а не как символы. У символа всегда есть пакет-владелец. Особый случай :keyword (тогда пакет владелец = «KEYWORD» и значение символа всеггда равно самому символу).

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

В разных фазах - разное значение. Тебе надо как то так делать:

#lang racket/base

(require (for-syntax racket/base))

(define-for-syntax names null)

(define-syntax (m stx)
  (syntax-case stx ()
    [(m name)
     (begin
       (set! names (cons #'name names))
       #'(void))]))

(define-syntax (get-names stx)
  (syntax-case stx ()
    [(_)
     #`(quote #,names)]))

(m a)
(m b)

> (get-names)
'(b a)

qaqa ★★
()
Ответ на: комментарий от qaqa
(module def racket/base 
  (provide m get-names (for-syntax names))
  (require (for-syntax racket/base))
  
  (define-for-syntax names null)
  
  (define-syntax (m stx)
    (syntax-case stx ()
      [(m name)
       (begin
         (set! names (cons #'name names))
         #'(void))]))
  
  (define-syntax (get-names stx)
    (syntax-case stx ()
      [(_)
       #`(quote #,names)]))
  
  (m a)
  (m b))

(require 'def)
> (get-names)
'()
monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Есть такое, поздно уже заметил. Если (get-names) вызывать в коде, то работает. А в REPL - тут видимо заморочки с namespace. Надо посмотреть документацию подробнее.

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

А в REPL - тут видимо заморочки с namespace.

По-моему, не с namespace, а с порядком компиляции.

Вот так забавно работает:

#lang racket
(module def racket/base 
  (provide m get-names (for-syntax names) real-names)
  (require (for-syntax racket/base))
  
  (define-for-syntax names null)
  
  (define-syntax (m stx)
    (syntax-case stx ()
      [(m name)
       (begin
         (set! names (cons #'name names))
         #'(void))]))
  
  (define-syntax (get-names stx)
    (syntax-case stx ()
      [(_)
       #`(quote #,names)]))
  
  (m a)
  (m b)
  
  (define real-names (get-names)))

(require 'def)
(m c)
(displayln (get-names))
;; run
(c)
> real-names
'(b a)
> (get-names)
'()

Отсюда можно сделать вывод, что Racket компилирует каждый модуль в отдельном «образе» (не знаю как правильно это назвать, потому что вроде не отдельный процесс, а поток, но с отдельным пространством имён). Получается что-то вроде системы сборки xcvb в Common Lisp. Осталось выяснить, гарантирован ли порядок развёртки макросов и вычисления аргументов функции (то есть, всегда ли слева направо и в случае top-level всегда ли функция выполняется до начала развёртки следующего макроса).

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

Отсюда можно сделать вывод, что Racket компилирует каждый модуль в отдельном «образе»

Именно. А как еще? Иначе куча принципиально неустранимых проблем возникает.

в случае top-level

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

В топлевее все выполняется так как и может в топлевеле - то есть по очереди тут варинтов нет. Сперва экспанд потом евал. В модуллевеле или internal definition сперва раскрываются внешни макросы (пока можно)

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

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

А как рекомендуется вставлять метаинформацию для использования макросами дальше? Например правила синтаксического разбора. Имеет смысл сделать макрос для описания правила и макрос для разбора. Как из первого во второй передать информацию?

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

(define-rule my-rule ...) -> (define-syntax my-rule (rule ...)), где rule - структура с нужной информацией, потом в другом макросе зачение можно получить через (syntax-local-value #'my-rule)

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

И это с forward declaration кстати будет работать: то есть можно расположить описание правила под кодом его использующим и все будет работать, если код завернут в санку. В этом случа описание правила раскроется при первом проходе экспандера а санка на вторлм, так что ее полозение (выше ии ниже определения) не важно

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

Именно так например match работает - макрос struct связывает с именем струкры метаинформацию которую потом match использует при разборе. На эту тему paper есть, macros that work together, если не ошибаюсь

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

(define-rule my-rule ...) -> (define-syntax my-rule (rule ...))

А почему тогда не define-for-syntax ? В чём разница?

В этом случа описание правила раскроется при первом проходе экспандера а санка на вторлм

Немножко не понял.

((lambda ()
  (define-syntax compile ... rule1 ... rule2)
  (compile))

(define-syntax rule1 ...)
(define-syntax rule2 ...)
(define-syntax rule3 ...)

будет работать ? То есть локальный макрос вообще не раскрывается до начала выполнения?

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

define-syntax - опрееляет идентификатор 0-фазы, работае в internal-definition контексте, корректно шадовится и работает с локальным скопом. define-for-syntax - +1-фаза, работает только в топлевеле и модульлевеле. На счет санки имелось ввиду не то, почитай paper, там вроде про это было, я не смогу объяснить без примеров.

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

Посмотри в макростеппере в каком порядке расркываются формы в modul/internal definition контексте (только выруби сокрытие шагов), тогда поймешь все

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

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

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

Без двупроходного экспандера нельзя было бы определять взаимнорекурсивные функции

А разве там не letrec ? Который грубо выглядит как

(letrec ((func1 val) (func2 val2)) body ...) =>

(let ((func1 undef) (func2 undef))
  (set! func1 val1)
  (set! func2 val2)
  body ...)

Надо для понимания действительно сначала paper почитать.

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

В module-level не letrec. В internal definitions - letrec, но чтобы преоьразоватб в летрек, надо же сначала раскрытб все внутренние формы (ведь может быть макрос раскрывающийся в дефайн), и если расркывать последовательно то мы наткнемся на несвязанную переменную. Посмотри порядок раскрытия в макросеппере все сразу намного понятнее станет

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

надо же сначала раскрытб все внутренние формы (ведь может быть макрос раскрывающийся в дефайн)

Магия:

> (syntax->datum  (expand '(let () 1 (define a 1) (define b 2) (cons a b))))
'(let-values ()
   (let-values ((() (begin '1 (#%app values)))) (let-values (((a) '1)) (let-values (((b) '2)) (#%app cons a b)))))
> (syntax->datum  (expand '(let () 1 (define a (lambda () b)) (define b 2) (cons a b))))
'(let-values ()
   (let-values ((() (begin '1 (#%app values)))) (letrec-values (((a) (lambda () b)) ((b) '2)) (#%app cons a b))))

Форма (let или letrec) зависит от значения определяемой переменной. Буду читать, как так можно. Через многократный expand-once посмотрел: там какое-то шаманство внутри letrec-syntaxes+values. На CL для этого эффекта пришлось бы вручную macroexpand вызывать, а потом codewalker'ом проверять.

А почему expand/step (и macrostepper) отказывается эту форму раскрывать? Я хотел сначала через него посмотреть, как оно так выходит.

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

Expand-once и иже с ним - бесполезная херня, забудь о ней вообще и пользуйся степпером. Let оно не раскрывает видимо изза того что ты не отключил сокрытие шагов. Там три галочки есть, каждая из которых соответствует сокрытию шагов >1 фазы, макросов занруженных библиотек и #%app/#%datum трансформеров

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

И лет/летрек не от значения зависит а от наличия forward declaration, по идее.

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

На CL для этого эффекта пришлось бы вручную macroexpand вызывать, а потом codewalker'ом проверять.

Именно. let/module формы делают partially local expand и смотрят, а не является ли расрытая форма дефайном, если является - добавляет в текущий лексический контекст соответствующее связывание. Потом раскрываются тела и проверяется наличие forward declaration чтобы опрещелить нежуен летрек или нет.

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

let/module формы делают partially local expand и смотрят

Странно, что в CL аналога local-expand нету. Хотя пишется через mapcar + macroexpand-1 тривиально. Правда традиции здесь отличаются очень сильно, может невостребовано. Не помню почти ни одного макроса, который бы анализировал свои внутренности (кроме iterate:iter разве что, но там полнофункциональный кодообходчик).

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

Спасибо! Кстати, не встречал ли какого-нибудь компилятора Scheme написанного на Common Lisp? Чуть-чуть есть у Норвига, но совсем чуть-чуть...

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

кстати твой пример если переписать как в статье работает (идентификаторы сохраняются в другом модуле).

не встречал ли какого-нибудь компилятора Scheme написанного на Common Lisp?

Не-а. Думаю, никто особо не горел такой идеей. Разве что игрушечные варианты.

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

Надо будет собрать твои последние треды и FAQ по ракето-макросам сделать, кстати

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