99 lines
3.6 KiB
TeX
99 lines
3.6 KiB
TeX
\begin{schemeregion}
|
|
\section{Macros}
|
|
\label{sect:macros}
|
|
|
|
PLT Scheme's macro system is based on the
|
|
hygienic~\cite{cr:mtw,kffd:hygiene} \scheme{syntax-case}
|
|
system~\cite{dhb:sc}, named after the syntactic form it provides for
|
|
destructuring the syntax of macro occurrences. A distinguishing aspect
|
|
of this system is its use of a syntax object system, a rich datatype
|
|
for representing program fragments.
|
|
|
|
The \scheme|syntax-case| system includes procedural macros, which have
|
|
two major advantages over the more widely known pattern-rewriting
|
|
macros:
|
|
|
|
\begin{itemize}
|
|
\item
|
|
Procedural macros can perform computation at compile time.
|
|
\item
|
|
Procedural macros allow the programmer to detect and report syntax
|
|
errors. Macro writers can enforce constraints on legal syntax (e.g., that
|
|
a given list of identifiers must not contain duplicates); detect when
|
|
those constraints are violated; and report errors in an appropriate,
|
|
context-specific fashion.
|
|
\end{itemize}
|
|
|
|
The macro definition in Figure~\ref{f:getset} demonstrates the major
|
|
capabilities of \scheme{syntax-case} macros. Its purpose is to create
|
|
procedures that access and update a shared, hidden variable.
|
|
%
|
|
For example, a programmer can write
|
|
\scheme{(define-getter+setter balance)} to create definitions for
|
|
\scheme{get-balance} and \scheme{set-balance!}.
|
|
|
|
\begin{figure}
|
|
\begin{schemedisplay}
|
|
(define-syntax (define-getter+setter stx)
|
|
;; symbol-append : symbol ... $\rightarrow$ symbol
|
|
(define (symbol-append . syms)
|
|
(string->symbol (apply string-append (map symbol->string syms))))
|
|
(syntax-case stx ()
|
|
[(define-getter+setter name init-value)
|
|
;; constraint checking:
|
|
(unless (identifier? #'name)
|
|
(raise-syntax-error 'define-get+set "expected identifier" #'name))
|
|
;; transformation:
|
|
(with-syntax
|
|
([getter (datum->syntax
|
|
#'name
|
|
(symbol-append 'get- (syntax->datum #'name)))]
|
|
[setter (datum->syntax
|
|
#'name
|
|
(symbol-append 'set- (syntax->datum #'name) '!))])
|
|
#'(define-values (getter setter)
|
|
(let ([name init-value])
|
|
(values (lambda () name)
|
|
(lambda (new-value) (set! name new-value))))))]))
|
|
\end{schemedisplay}
|
|
\caption{A \textbf{syntax-case} macro}
|
|
\label{f:getset}
|
|
\end{figure}
|
|
|
|
The macro defines a procedural abstraction (\scheme{symbol-append}) to
|
|
help construct names. Within the \scheme{syntax-case} clause, the
|
|
macro checks that the given name is an identifier (a syntax object
|
|
containing a symbol); otherwise, it raises an error. Then it uses the
|
|
macro system's \scheme{datum->syntax} procedure together with
|
|
its own \scheme{symbol-append} abstraction to construct the names of
|
|
the getter and setter procedures. This macro breaks hygiene, because
|
|
the hygiene principle states that introduced names only capture
|
|
references to the same name that are introduced by the same macro
|
|
transformation.
|
|
\end{schemeregion}
|
|
|
|
|
|
% The standard \scheme{syntax-case} system defines a few procedures that
|
|
%constitute an API to the macro expander, including
|
|
%\scheme{datum->syntax} and \scheme{syntax->datum}.
|
|
|
|
%
|
|
%% These, along with the others used in this paper, are given with their
|
|
%% contracts in figure \ref{f:api}.
|
|
|
|
%% \begin{figure}
|
|
%% \begin{schemedisplay}
|
|
%% ;; syntax-e : syntax $\rightarrow$ datum
|
|
%% ;; strips off one layer of syntax object wrapping
|
|
%% (syntax-e #'(a b c)) ;$\Rightarrow$ \scheme{(list #'a #'b #'c)}
|
|
|
|
%% ;; datum$\rightarrow$syntax : syntax datum $\rightarrow$ syntax
|
|
%% ;; constructs a new syntax object with the given context
|
|
%% (datum->syntax #'here '(+ 1 2)) ;$\Rightarrow$ \scheme{#'(+ 1 2)}
|
|
|
|
%% \end{schemedisplay}
|
|
%% \caption{Macro API}
|
|
%% \label{f:api}
|
|
%% \end{figure}
|
|
|