2016-12-14 2 views
2

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

Привязка формальных параметров в теле не проблема, но привязка локального состояния кажется сложнее. В примере в конце кода ниже, в теле save, a останется несвязанным, несмотря на то, что a (другое a) связано сгенерированной функцией evaluate-body в макросе METHOD. Критическая точка в образце коды ниже, таким образом, METHOD макро, более конкретно evaluate-body функции (что, где связывание должно произойти, это означает, что мой дизайн программы является разумным)

Есть ли способ гигиенический связать этот произвольный набор бесплатные идентификаторы (в настоящее время только содержащие a, но это может быть что-нибудь, действительно)?

#lang racket 

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

(struct actor (local-state methods)) 
(struct method (name formal-parameters body)) 

(define-syntax-parameter local-state-variables #f) 

(define-syntax (ACTOR stx) 
    (syntax-parse stx 
    [(_ (LOCAL_STATE state-variable ...) method:expr ...+) 
    #'(syntax-parameterize ([local-state-variables '(state-variable ...)]) 
     ; For the sake of simplicity, an actor is currently a list of message handlers 
     (actor 
      (make-list (length '(state-variable ...)) (void)) 
      (list method ...)))])) 


(define-syntax (METHOD stx) 
    (syntax-parse stx 
    [(_ (name:id formal-parameter:id ...) body:expr ...+) 
    (with-syntax ([(local-state-variable ...) (syntax-parameter-value #'local-state-variables)]) 
     #'(method 
      'name 
      '(formal-parameter ...) 
      (λ (formal-parameter ... #:local-state [current-state '()]) 
      ; the "a" that will be bound here is different from the free identifier "a" in the body 
      (define (evaluate-body local-state-variable ...) 
       body ... 
       (list local-state-variable ...)) 
      (apply evaluate-body current-state))))])) 


(ACTOR (LOCAL_STATE a) 
     (METHOD (save new-a) 
       ; "a" is an unbound identifier 
       (set! a new-a))) 

ответ

1

Для того чтобы локальные переменные состояния имели правильный лексический контекст, вам необходимо сохранить их как идентификаторы, а не символы. То есть, в результате ACTOR макроса, вам необходимо изменить syntax-parameterize к этому:

#'(syntax-parameterize ([local-state-variables #'(state-variable ...)]) 
    #| rest of the template (unchanged)... |#) 

Примечание замены quote/' с syntax/#'. Это сохранит идентификаторы с их лексическим контекстом, а не как символы.

Следующий шаг - правильно ввести их в макрос METHOD. Для этого вам просто нужно применить syntax-local-introduce к значению параметра синтаксиса, который добавит область ввода макроса к идентификаторам. Вы можете также заменить with-syntax с #:with пунктом syntax-parse «s, чтобы упростить вещи немного, так что общий макрос становится этим:

(define-syntax (METHOD stx) 
    (syntax-parse stx 
    [(_ (name:id formal-parameter:id ...) body:expr ...+) 
    #:with (local-state-variable ...) 
      (syntax-local-introduce (syntax-parameter-value #'local-state-variables)) 
    #'(method #| rest of the template (unchanged)... |#)])) 

Это будет работать.


Причина syntax-local-introduce нужен здесь может быть немного запутанным, но наиболее интуитивный способ думать о нем, рассматривая «наборов областей» модели гигиены, которые в настоящее время использует ракетку. Чтобы макросвязанные привязки не конфликтуют с пользовательскими привязками, каждая часть синтаксиса, возвращаемая синтаксическим трансформатором, имеет при себе новую область видимости, область, которая никогда не будет прикреплена ни к чему написанному пользователем. Конечно, некоторые из синтаксиса в результате - это синтаксис, предоставленный пользователем, поэтому макроэкспандер должен убедиться, что он не прикрепляет свежую область к этим синтаксическим объектам.

В общем случае невозможно выяснить, какие объекты синтаксиса должны считаться предоставленными пользователем, поскольку авторы макроса могут «согнуть» гигиену и создать новые синтаксические объекты из других. Решение, к счастью, прост и элегантен: просто прикрепите область введения макроса ко всем синтаксическим объектам, предоставленным пользователем до, передав их макросу, затем flip области для всех фрагментов синтаксиса в результате.Таким образом, предоставленные пользователем объекты синтаксиса не будут иметь область ввода макроса после перевертывания.

Функция syntax-local-introduce позволяет вам перевернуть эту специальную область вручную. В этом случае, поскольку значение local-state-variables должно рассматриваться как ввод макроса, но макросмендеру автоматически не присваивается область макрообмена (поскольку он не является прямым вводом макроса), вы должны добавьте масштаб самостоятельно. Таким образом, макроэкспандер удалит область после того, как макрос будет расширен, а идентификатор будет содержать правильный лексический контекст.

+0

Это потрясающе. К настоящему времени я хорошо знаком с крупными частями макросистемы (как ее использовать), но получаю глубокое понимание того, как все это _works_ все еще довольно сложно. Спасибо за этот проницательный ответ! – Sam