[icfp] impl part II, again
This commit is contained in:
parent
0250b7b109
commit
b50773ee46
|
@ -1,7 +1,7 @@
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tabular}{l l l}
|
\begin{tabular}{l l l}
|
||||||
Syntax Class & Purpose \\\hline
|
Syntax Class & Purpose \\\hline
|
||||||
\mod{fun/arity} & Infer function arity \\
|
\mod{fun/domain} & Infer function domain types \\
|
||||||
\mod{num/value} & Evaluate a numeric expression \\
|
\mod{num/value} & Evaluate a numeric expression \\
|
||||||
\mod{pattern/groups} & Count regexp groups \\
|
\mod{pattern/groups} & Count regexp groups \\
|
||||||
\mod{query/constr} & Parse \mod{SQL} queries \\
|
\mod{query/constr} & Parse \mod{SQL} queries \\
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#lang scribble/sigplan
|
#lang scribble/sigplan
|
||||||
@require["common.rkt"]
|
@require["common.rkt"]
|
||||||
|
|
||||||
@title[#:tag "sec:implementation"]{Implementation}
|
@title[#:tag "sec:implementation"]{More than a Pretty Face}
|
||||||
@; Amazing Macros
|
@; Amazing Macros
|
||||||
@; Whirlwind tour
|
@; Whirlwind tour
|
||||||
@; Accounting, taking stock
|
@; Accounting, taking stock
|
||||||
|
@ -49,12 +49,13 @@ Much of the brevity is due to amortizing helper functions, so we include helpers
|
||||||
|
|
||||||
|
|
||||||
@; -----------------------------------------------------------------------------
|
@; -----------------------------------------------------------------------------
|
||||||
@section[#:tag "sec:impl-trans"]{Implementing Transformations} @; TODO not a great name
|
@section[#:tag "sec:impl-elab"]{Elegant Elaborations}
|
||||||
@; @section{Ode to Macros: The Long Version}
|
|
||||||
|
|
||||||
At this point, we have carried on long enough talking about the implementation
|
Our elaboration for @racket[vector-length] is straightforward.
|
||||||
without actually showing any code.
|
If called with a size-annotated vector @racket[v], @racket[(vector-length v)]
|
||||||
No longer---here is our definition of @racket[vector-length]:
|
elaborates to the size.
|
||||||
|
Otherwise, it defaults to Typed Racket's @racket[vector-length].
|
||||||
|
The implementation is equally concise, modulo some notation.
|
||||||
|
|
||||||
@codeblock{
|
@codeblock{
|
||||||
(make-alias #'vector-length
|
(make-alias #'vector-length
|
||||||
|
@ -64,18 +65,14 @@ No longer---here is our definition of @racket[vector-length]:
|
||||||
[_ #false]))
|
[_ #false]))
|
||||||
}
|
}
|
||||||
|
|
||||||
First of all, this transformation works as specified in @Secref{sec:vector}.
|
|
||||||
When the length of its argument is known, it expands to that length.
|
|
||||||
Otherwise, it expands to an ordinary call to @racket[vector-length].
|
|
||||||
|
|
||||||
Second, we need to introduce a few mysterious characters:
|
|
||||||
@itemlist[
|
@itemlist[
|
||||||
@item{
|
@item{
|
||||||
@racket[(make-alias id f)] creates a transformation from an identifier @racket[id]
|
@racket[(make-alias id f)] creates a elaboration from an identifier @racket[id]
|
||||||
and a partial function @racket[f].
|
and a partial function @racket[f].
|
||||||
}
|
}
|
||||||
@item{
|
@item{
|
||||||
The symbol @tt{#'} creates a syntax object from a value or template.
|
The symbol @exact|{\RktMeta{\#'}}| creates a syntax object from a value or template.
|
||||||
}
|
}
|
||||||
@item{
|
@item{
|
||||||
A @racket[syntax-parser] is a match statement over syntactic patterns.
|
A @racket[syntax-parser] is a match statement over syntactic patterns.
|
||||||
|
@ -84,41 +81,41 @@ Second, we need to introduce a few mysterious characters:
|
||||||
wildcard @tt{_}.
|
wildcard @tt{_}.
|
||||||
}
|
}
|
||||||
@item{
|
@item{
|
||||||
The colon character (@tt{:}) used int @racket[v:vector/length]
|
The colon character (@tt{:}) used in @racket[v:vector/length]
|
||||||
binds the variable @racket[v] to the @emph{syntax class} @racket[vector/length].
|
binds the variable @racket[v] to the @emph{syntax class} @racket[vector/length].
|
||||||
}
|
}
|
||||||
@item{
|
@item{
|
||||||
The dot character (@tt{.}) accesses an @emph{attribute} of the value bound
|
The dot character (@tt{.}) accesses an @emph{attribute} of the value bound
|
||||||
to @racket[v].
|
to @racket[v].
|
||||||
In this case, the attribute @racket[evidence] is set when
|
In this case, the attribute @racket[evidence] is set when the class
|
||||||
@racket[vector/length] matches successfully.
|
@racket[vector/length] successfully matches the value of @racket[v].
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
Third, we remark that the pattern @racket[v:vector/length] unfolds all
|
The pattern @racket[v:vector/length] unfolds all
|
||||||
transformations to @racket[v] recursively.
|
elaborations to @racket[v] recursively.
|
||||||
So we handle each of the following cases, as well as any other combination of
|
So, as hinted in @Secref{sec:vector},
|
||||||
|
we handle each of the following cases as well as any other combination of
|
||||||
length-preserving vector operations.
|
length-preserving vector operations.
|
||||||
|
|
||||||
@racketblock[
|
@racketblock[
|
||||||
> '(vector-length #(H I))
|
> (vector-length #(0 1 2))
|
||||||
2
|
2
|
||||||
> '(vector-length (vector-append #(Y O)
|
> (vector-length (vector-append #(A B)
|
||||||
#(L O)))
|
#(C D)))
|
||||||
4
|
4
|
||||||
]
|
]
|
||||||
|
|
||||||
@; The general features are explained in greater deteail below
|
@; The general features are explained in greater deteail below
|
||||||
|
|
||||||
@; make-alias
|
@; make-alias
|
||||||
@; TODO variable name for f
|
The structure of @racket[vector-length] is common to many of our elaborations:
|
||||||
The overall structure of @racket[vector-length] is common to many of our transformations.
|
we define a rule to handle an interesting syntactic pattern and
|
||||||
That is, we define a rule to handle an interesting syntactic pattern and
|
|
||||||
then generate an alias from the rule using the helper function @racket[make-alias].
|
then generate an alias from the rule using the helper function @racket[make-alias].
|
||||||
|
|
||||||
@codeblock{
|
@codeblock{
|
||||||
(define ((make-alias orig-id f) stx)
|
(define ((make-alias orig-id elaborate) stx)
|
||||||
(or (f stx)
|
(or (elaborate stx)
|
||||||
(syntax-parse stx
|
(syntax-parse stx
|
||||||
[_:id
|
[_:id
|
||||||
orig-id]
|
orig-id]
|
||||||
|
@ -126,11 +123,10 @@ That is, we define a rule to handle an interesting syntactic pattern and
|
||||||
#`(#,orig-id e* ...)])))
|
#`(#,orig-id e* ...)])))
|
||||||
}
|
}
|
||||||
|
|
||||||
The transformation defined by @racket[(make-alias id f)] is a function on
|
The elaboration defined by @racket[(make-alias id elaborate)] is a function on
|
||||||
syntax objects.
|
syntax objects.
|
||||||
First, the function applies @racket[f] to the syntax object @racket[stx].
|
This function first applies @racket[elaborate] to the syntax object @racket[stx].
|
||||||
If the result is not
|
If the result is not @racket[#false] we return.
|
||||||
@racket[#false] we return.
|
|
||||||
Otherwise the function matches its argument against two possible patterns:
|
Otherwise the function matches its argument against two possible patterns:
|
||||||
@itemize[
|
@itemize[
|
||||||
@item{
|
@item{
|
||||||
|
@ -141,29 +137,29 @@ Otherwise the function matches its argument against two possible patterns:
|
||||||
@racket[(_ e* ...)] matches function application.
|
@racket[(_ e* ...)] matches function application.
|
||||||
In the result of this branch,
|
In the result of this branch,
|
||||||
@; TODO backtick not printing right
|
@; TODO backtick not printing right
|
||||||
we declare a syntax template with @tt{#`} and splice the identifier
|
we declare a syntax template with @exact|{\RktMeta{\#`}}| and splice the identifier
|
||||||
@racket[orig-id] into the template with @tt{#,}.
|
@racket[orig-id] into the template with @exact|{\RktMeta{,\#}}|.
|
||||||
These operators are formally known as @racket[quasisyntax] and @racket[unsyntax];
|
These operators are formally known as @racket[quasisyntax] and @racket[unsyntax];
|
||||||
you may know their cousins @racket[quasiquote] and @racket[unquote].
|
you may know their cousins @racket[quasiquote] and @racket[unquote].
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@emph{Note:} the identifier @racket[...] is not pseudocode!
|
The identifier @racket[...] is not pseudocode.
|
||||||
In a pattern, it captures zero-or-more repetitions of the preceding pattern---in
|
In a pattern, it captures zero-or-more repetitions of the preceding pattern---in
|
||||||
this case, the variable @racket[e*] binds anything so @racket[(_ e* ...)] matches
|
this case, the variable @racket[e*] binds anything so @racket[(_ e* ...)] matches
|
||||||
lists with at least one element.@note{The name @racket[e*] is our own convention.}
|
lists with at least one element.@note{The variable name @racket[e*] is our own convention.}
|
||||||
All but the first element of such a list is then bound to the identifier
|
All but the first element of such a list is then bound to the identifier
|
||||||
@racket[e*] in the result.
|
@racket[e*] in the result.
|
||||||
We use @racket[...] in the result to flatten the contents of @racket[e*] into
|
We use @racket[...] in the result to flatten the contents of @racket[e*] into
|
||||||
the final expression.
|
the final expression.
|
||||||
|
|
||||||
One last example transformation using @racket[make-alias]
|
A second example using @racket[make-alias]
|
||||||
is our definition of @racket[vector-ref], shown below.
|
is @racket[vector-ref] @exact{$\in \elab$}, shown below.
|
||||||
When given a sized vector @racket[v] and an expression @racket[e] that
|
When given a sized vector @racket[v] and an expression @racket[e] that
|
||||||
expands to a number @racket[i], the function asserts that @racket[i] is
|
expands to a number @racket[i], the function asserts that @racket[i] is
|
||||||
in bounds.
|
in bounds.
|
||||||
If either @racket[vector/length] or @racket[expr->num] fail to coerce numeric
|
If either @racket[vector/length] or @racket[expr->num] fail to coerce numeric
|
||||||
values, the function returns @racket[#false].
|
values, the function defaults to Typed Racket's @racket[vector-ref].
|
||||||
|
|
||||||
@codeblock{
|
@codeblock{
|
||||||
(make-alias #'vector-ref
|
(make-alias #'vector-ref
|
||||||
|
@ -178,12 +174,12 @@ If either @racket[vector/length] or @racket[expr->num] fail to coerce numeric
|
||||||
[_ #false]))
|
[_ #false]))
|
||||||
}
|
}
|
||||||
|
|
||||||
Unlike the previous two functions, our @racket[vector-ref] transformation
|
This elaboration
|
||||||
does more than just matching a pattern and returning a new syntax object.
|
does more than just matching a pattern and returning a new syntax object.
|
||||||
Crucially, it compares the @emph{value} used to index its argument vector with
|
Crucially, it compares the @emph{value} used to index its argument vector with
|
||||||
that vector's length before choosing how to expand.
|
that vector's length before choosing how to expand.
|
||||||
To access these integer values outside of a template, we lift the pattern variables
|
To access these integer values outside of a template, we lift the pattern variables
|
||||||
@racket[v] and @racket[e] to syntax objects with a @tt{#'} prefix.
|
@racket[v] and @racket[e] to syntax objects with a @exact|{\RktMeta{\#'}}| prefix.
|
||||||
A helper function @racket[expr->num] then fully expands the syntax object @racket[#'e]
|
A helper function @racket[expr->num] then fully expands the syntax object @racket[#'e]
|
||||||
and the built-in @racket[syntax->datum] gets the integer value stored at the
|
and the built-in @racket[syntax->datum] gets the integer value stored at the
|
||||||
attribute @racket[#'v.evidence].
|
attribute @racket[#'v.evidence].
|
||||||
|
@ -195,96 +191,102 @@ The interesting design challenge is making one pattern that covers all
|
||||||
|
|
||||||
|
|
||||||
@; =============================================================================
|
@; =============================================================================
|
||||||
@section[#:tag "sec:impl-interp"]{Implementing Interpretations} @; TODO a decidedly bad name
|
@section[#:tag "sec:impl-interp"]{Illustrative Interpretations}
|
||||||
|
|
||||||
By now we have seen two useful syntax classes: @racket[id] and @racket[vector/length].
|
By now we have seen two useful syntax classes: @racket[id] and
|
||||||
|
@racket[vector/length].@note{The name @racket[vector/length] should
|
||||||
|
be read as ``vector @emph{with} length information''.}
|
||||||
In fact, we use syntax classes as the front-end for each function in @exact{$\interp$}.
|
In fact, we use syntax classes as the front-end for each function in @exact{$\interp$}.
|
||||||
@Figure-ref{fig:stxclass} lists all of our syntax classes and ties each to a purpose
|
@Figure-ref{fig:stxclass} lists all of our syntax classes and ties each to a purpose
|
||||||
motivated in @Secref{sec:usage}.@note{The name @racket[vector/length] should
|
motivated in @Secref{sec:usage}.
|
||||||
be read as ``vector @emph{with} length information''.}
|
|
||||||
|
|
||||||
@figure["fig:stxclass"
|
@figure["fig:stxclass"
|
||||||
"Registry of syntax classes"
|
"Registry of syntax classes"
|
||||||
@exact|{\input{fig-stxclass}}|
|
@exact|{\input{fig-stxclass}}|
|
||||||
]
|
]
|
||||||
|
|
||||||
These classes are implemented uniformly from predicates on syntax objects.
|
The definitions of these classes are generated from predicates on syntax
|
||||||
One such predicate is @racket[arity?], shown below, which counts
|
objects.
|
||||||
the parameters accepted by an uncurried anonymous function and returns
|
One such predicate is @racket[vector?], shown below, which counts the
|
||||||
@racket[#false] for all other inputs.
|
length of vector values and returns @racket[#false] for all other inputs.
|
||||||
|
Notice that the pattern for @racket[make-vector] recursively expands its
|
||||||
|
first argument using the @racket[num/value] syntax class.
|
||||||
|
|
||||||
@codeblock{
|
@codeblock{
|
||||||
(define arity?
|
(define vector?
|
||||||
(syntax-parser #:literals (λ)
|
(syntax-parser #:literals (make-vector)
|
||||||
[(λ (x*:id ...) e* ...)
|
[#(e* ...)
|
||||||
(length (syntax->datum #'(x* ...)))]
|
(length (syntax->datum #'(e* ...)))]
|
||||||
|
[(make-vector n:num/value)
|
||||||
|
(syntax->datum #'n.evidence)]
|
||||||
[_ #f]))
|
[_ #f]))
|
||||||
}
|
}
|
||||||
|
|
||||||
The syntax class @racket[procedure/arity] is then defined as ...
|
|
||||||
|
|
||||||
@racketblock[
|
From @racket[vector?], we define the syntax class @racket[vector/length]
|
||||||
> (define-stxclass/pred procedure/arity
|
that handles the mechanical work of macro-expanding its input,
|
||||||
arity?)
|
applying the @racket[vector?] predicate, and caching results in the
|
||||||
]
|
@racket[evidence] and @racket[expanded] attributes.
|
||||||
|
|
||||||
... in terms of another macro, which handles the routine work of recursively
|
|
||||||
expanding its input, applying the @racket[arity?] predicate,
|
|
||||||
and caching results in the @racket[evidence] and @racket[expanded] attributes.
|
|
||||||
|
|
||||||
@codeblock{
|
@codeblock{
|
||||||
(define-syntax-rule (define-stxclass/pred id p?)
|
(define-syntax-class vector/length
|
||||||
(define-syntax-class id
|
#:attributes (evidence expanded)
|
||||||
#:attributes (evidence expanded)
|
(pattern e
|
||||||
(pattern e
|
#:with e+ (expand-expr #'e)
|
||||||
#:with e+ (expand-expr #'e)
|
#:with len (vector? #'e+)
|
||||||
#:with p+ (p? #'e+)
|
#:when (syntax->datum #'len)
|
||||||
#:when (syntax->datum #'p+)
|
#:attr evidence #'len
|
||||||
#:attr evidence #'p+
|
#:attr expanded #'e+))
|
||||||
#:attr expanded #'e+)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
A @racket[define-syntax-rule] is an inlined definition; using it here does not
|
The @racket[#:attributes] declaration is key.
|
||||||
save any space, but in practice we re-use the same alias for each of
|
|
||||||
our custom syntax classes.
|
|
||||||
The @racket[#:attributes] declaration is very important.
|
|
||||||
This is where the earlier-mentioned @racket[v.expanded] and @racket[v.evidence]
|
This is where the earlier-mentioned @racket[v.expanded] and @racket[v.evidence]
|
||||||
were defined, and indeed these two attributes form the backbone of our value-parsing
|
properties are defined, and indeed these two attributes form the backbone
|
||||||
protocol.
|
of our protocol for cooperative elaborations.
|
||||||
In terms of a pattern @racket[x:procedure/arity], their meaning is:
|
In terms of a pattern @racket[v:vector/length], their meaning is:
|
||||||
@itemlist[
|
@itemlist[
|
||||||
@item{
|
@item{
|
||||||
@racket[x.expanded] is the result of fully expanding all macros and transformations
|
@racket[v.expanded] is the result of fully expanding all macros and elaborations
|
||||||
contained in the syntax object bound to @racket[x].
|
contained in the syntax object bound to @racket[v].
|
||||||
The helper function @racket[expand-expr] triggers this depth-first expansion.
|
The helper function @racket[expand-expr] triggers this depth-first expansion.
|
||||||
}
|
}
|
||||||
@item{
|
@item{
|
||||||
@racket[x.evidence] is the result of applying the @racket[arity?] predicate
|
@racket[v.evidence] is the result of applying the @racket[vector?] predicate
|
||||||
to the expanded version of @racket[x].
|
to the expanded version of @racket[v].
|
||||||
Intuitively, @racket[x.evidence] is the reason why we should be able to
|
In general, @racket[v.evidence] is the reason why we should be able to
|
||||||
perform transformations using @racket[x].
|
perform elaborations using the value bound to @racket[v].
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
If the predicate @racket[arity?] returns @racket[#false], then the boolean
|
If the predicate @racket[vector?] returns @racket[#false] then the boolean
|
||||||
@racket[#:when] guard fails because the value contained in the syntax object
|
@racket[#:when] guard fails because the value contained in the syntax object
|
||||||
@racket[p+] will be @racket[#false].
|
@racket[len] will be @racket[#false].
|
||||||
When this happens, neither attribute is bound and the pattern
|
When this happens, neither attribute is bound and the pattern
|
||||||
@racket[x.procedure/arity] will fail.
|
@racket[v.vector/length] will fail.
|
||||||
|
|
||||||
|
|
||||||
@; =============================================================================
|
@; =============================================================================
|
||||||
@section{Implementing Definitions}
|
@section{Automatically Handling Variables}
|
||||||
|
|
||||||
With that, we have essentially finished our tour of the key ideas underlying
|
When the results of an elaboration are bound to a variable @racket[v],
|
||||||
our implementation.
|
we frequently need to associate a compile-time value to @racket[v] for
|
||||||
The one detail we elided is precisely how interpreted data is propogated upward
|
later elaborations to use.
|
||||||
through recursive transformations, especially since transformations may unfold
|
This is often the case for calls to @racket[sql-connect]:
|
||||||
into arbitrary, difficult-to-parse code.
|
|
||||||
|
|
||||||
An illustrative example is our transformation for @racket[sql-connect],
|
@racketblock[
|
||||||
the library function for connecting a user to a database.
|
(define C (sql-connect ....))
|
||||||
Recall that our library imposes an extra constraint on calls to @racket[sql-connect]:
|
(query-row C ....)
|
||||||
they must supply a database schema, which is erased in translation.
|
]
|
||||||
|
|
||||||
|
Reading the literal variable @racket[C] gives no useful information
|
||||||
|
when elaborating the call to @racket[query-row].
|
||||||
|
Instead, we need to retrieve the database schema for the connection bound to @racket[C].
|
||||||
|
|
||||||
|
The solution starts with our implementation of @racket[sql-connect],
|
||||||
|
which uses the built-in function
|
||||||
|
@racket[syntax-property] to associate a key/value pair with a syntax object.
|
||||||
|
Future elaborations on the syntax object @racket[#'(sql-connect e* ...)] can
|
||||||
|
retrieve the database schema @racket[#'s.evidence] by using the key @racket[connection-key].
|
||||||
|
|
||||||
@racketblock[
|
@racketblock[
|
||||||
(syntax-parser
|
(syntax-parser
|
||||||
|
@ -297,45 +299,16 @@ Recall that our library imposes an extra constraint on calls to @racket[sql-conn
|
||||||
"Missing schema")])
|
"Missing schema")])
|
||||||
]
|
]
|
||||||
|
|
||||||
Most of this definition is routine.
|
Storing this syntax property is the job of the programmer, but we automate
|
||||||
We use the syntax class @racket[schema/spec] to lift schema specifications to
|
the task of bubbling the property up through variable definitions by overriding
|
||||||
the compile-time environment and we ultimately forward all non-schema arguments
|
Typed Racket's @racket[define] and @racket[let] forms.
|
||||||
to the default @racket[sql-connect].@note{If an one of the arguments
|
New definitions search for specially-keyed properties like @racket[connection-key];
|
||||||
@racket[e* ...] is malformed, this will be reported by the original
|
when found, they associate their variable with the property in a local hashtable
|
||||||
@racket[sql-connect]. Three cheers for division of labor!}
|
whose keys are @exact{$\alpha$}-equivalence classes of identifiers.
|
||||||
The new form is @racket[syntax-property], which tags our new syntax object
|
New @racket[let] bindings work similarly, but redirect variable references
|
||||||
with a key/value pair.
|
within their scope.
|
||||||
Here the key is @racket[connection-key], which we generate when compiling a file
|
The technical tools for implementing these associations are @racket[free-id-table]s
|
||||||
and use to identify connection objects.
|
and @racket[rename-transformer]s (@Secref{sec:rename}).
|
||||||
The value is the evidence parsed from the schema description.
|
|
||||||
|
|
||||||
Transformation writers must take care to install @racket[syntax-property]
|
|
||||||
information, but we automate the task of retrieving cached properties
|
|
||||||
in our syntax classes---before
|
|
||||||
applying a predicate, we first search for a cached value.
|
|
||||||
Syntax properties are likewise the trick for propagating metadata through
|
|
||||||
@racket[let] and @racket[define] bindings.
|
|
||||||
The technical tools for this are @racket[rename-transformer]s and @racket[free-id-table]s,
|
|
||||||
which we discuss in @Secref{sec:rename}.
|
|
||||||
|
|
||||||
@;@codeblock{
|
|
||||||
@; (make-alias #'vector-append
|
|
||||||
@; (syntax-parser
|
|
||||||
@; [(_ v0:vector/length v1:vector/length)
|
|
||||||
@; (define len0 (syntax-e #'v1.evidence))
|
|
||||||
@; (define len1 (syntax-e #'v2.evidence))
|
|
||||||
@; (define new-len (+ len0 len1))
|
|
||||||
@; (syntax-property
|
|
||||||
@; #`(build-vector
|
|
||||||
@; #,new-len
|
|
||||||
@; (lambda (i)
|
|
||||||
@; (if (< i '#,len0)
|
|
||||||
@; (unsafe-vector-ref v1.expanded i)
|
|
||||||
@; (unsafe-vector-ref v2.expanded i)))
|
|
||||||
@; vector-length-key
|
|
||||||
@; new-len))]
|
|
||||||
@; [_ #f]))
|
|
||||||
@;}
|
|
||||||
|
|
||||||
|
|
||||||
@; -----------------------------------------------------------------------------
|
@; -----------------------------------------------------------------------------
|
||||||
|
@ -343,20 +316,19 @@ The technical tools for this are @racket[rename-transformer]s and @racket[free-i
|
||||||
|
|
||||||
@; Symphony of features
|
@; Symphony of features
|
||||||
|
|
||||||
Whereas the previous section was a code-first tour of key techniques supporting
|
This section is a checklist of important meta-programming
|
||||||
our implementation, this section is a checklist of important meta-programming
|
|
||||||
tools provided by the Racket macro system.
|
tools provided by the Racket macro system.
|
||||||
For ease of reference, our discussion proceeds from the most useful tool to
|
Each sub-section title is a technical term;
|
||||||
|
for ease of reference, our discussion proceeds from the most useful tool to
|
||||||
the least.
|
the least.
|
||||||
Each sub-section title is a technical term.
|
|
||||||
@;Titles marked with an asterisk are essential to our implementation,
|
@;Titles marked with an asterisk are essential to our implementation,
|
||||||
@; others could be omitted without losing the essence.
|
@; others could be omitted without losing the essence.
|
||||||
|
|
||||||
|
|
||||||
@subsection[#:tag "sec:parse"]{Syntax Parse}
|
@subsection[#:tag "sec:parse"]{Syntax Parse}
|
||||||
|
|
||||||
The @racket[syntax/parse] library@~cite[c-dissertation-2010] is a powerful
|
The @racket[syntax/parse] library@~cite[c-dissertation-2010] provides
|
||||||
interface for writing macros.
|
tools for writing macros.
|
||||||
It provides the @racket[syntax-parse] and @racket[syntax-parser] forms that
|
It provides the @racket[syntax-parse] and @racket[syntax-parser] forms that
|
||||||
we have used extensively above.
|
we have used extensively above.
|
||||||
From our perspective, the key features are:
|
From our perspective, the key features are:
|
||||||
|
@ -364,13 +336,13 @@ From our perspective, the key features are:
|
||||||
@item{
|
@item{
|
||||||
A rich pattern-matching language; including, for example,
|
A rich pattern-matching language; including, for example,
|
||||||
repetition via @racket[...], @racket[#:when] guards, and matching
|
repetition via @racket[...], @racket[#:when] guards, and matching
|
||||||
for identifiers like @racket[λ] (top of @Secref{sec:impl-interp})
|
for identifiers like @racket[make-vector] (top of @Secref{sec:impl-interp})
|
||||||
that respects @exact{$\alpha$}-equivalence.
|
that respects @exact{$\alpha$}-equivalence.
|
||||||
}
|
}
|
||||||
@item{
|
@item{
|
||||||
Freedom to mix arbitrary code between the pattern spec and result,
|
Freedom to mix arbitrary code between the pattern spec and result,
|
||||||
as shown in the definition of @racket[vector-ref]
|
as shown in the definition of @racket[vector-ref]
|
||||||
(bottom of @Secref{sec:impl-trans}).
|
(bottom of @Secref{sec:impl-elab}).
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -382,12 +354,12 @@ Racket's macro expander normally proceeds in a breadth-first manner, traversing
|
||||||
After expansion, sub-trees are traversed and expanded.
|
After expansion, sub-trees are traversed and expanded.
|
||||||
This ``lazy'' sort of evaluation is normally useful because it lets macro
|
This ``lazy'' sort of evaluation is normally useful because it lets macro
|
||||||
writers specify source code patterns instead of forcing them to reason about
|
writers specify source code patterns instead of forcing them to reason about
|
||||||
the shape of expanded code.
|
the syntax trees of expanded code.
|
||||||
|
|
||||||
Our transformations, however, are most effective when value information is
|
Our elaborations, however, are most effective when value information is
|
||||||
propogated bottom up from macro-free syntactic values through other combinators.
|
propogated bottom up from macro-free syntactic values through other combinators.
|
||||||
This requires depth-first macro expansion; for instance, in the first argument
|
This requires depth-first macro expansion; for instance, in the first argument
|
||||||
of the @racket[vector-ref] transformation defined in @Secref{sec:impl-trans}.
|
of the @racket[vector-ref] elaboration defined in @Secref{sec:impl-elab}.
|
||||||
Fortunately, we always know which positions to expand depth-first and
|
Fortunately, we always know which positions to expand depth-first and
|
||||||
Racket provides a function @racket[local-expand] that will fully expand
|
Racket provides a function @racket[local-expand] that will fully expand
|
||||||
a target syntax object.
|
a target syntax object.
|
||||||
|
@ -399,15 +371,21 @@ In particular, all our syntax classes listed in @Figure-ref{fig:stxclass}
|
||||||
|
|
||||||
A syntax class encapsulates common parts of a syntax pattern.
|
A syntax class encapsulates common parts of a syntax pattern.
|
||||||
With the syntax class shown at the end of @Secref{sec:impl-interp}
|
With the syntax class shown at the end of @Secref{sec:impl-interp}
|
||||||
we save 2-6 lines of code in each of our transformation functions.
|
we save 2-6 lines of code in each of our elaboration functions.
|
||||||
More importantly, syntax classes provide a clean implementation of the ideas
|
More importantly, syntax classes provide a clean implementation of the ideas
|
||||||
in @Secref{sec:solution}.
|
in @Secref{sec:solution}.
|
||||||
Given a function in @exact{$\interp$} that extracts data from core value/expression
|
Given a function in @exact{$\interp$} that extracts data from core value/expression
|
||||||
forms, we generate a syntax class that applies the function and handles
|
forms, we generate a syntax class that applies the function and handles
|
||||||
intermediate binding forms.
|
intermediate binding forms.
|
||||||
Functions in @exact{$\trans$} can branch on whether the syntax class matched
|
Functions in @exact{$\elab$} can branch on whether the syntax class matched
|
||||||
instead of parsing data from program syntax.
|
instead of parsing data from program syntax.
|
||||||
|
|
||||||
|
In other words, syntax classes provide an interface that lets us reason
|
||||||
|
locally when writing elaborators.
|
||||||
|
The only question we ask during elaboration is whether a syntax object is associated
|
||||||
|
with an interpreted value---not how the object looks or what sequence of
|
||||||
|
renamings it filtered through.
|
||||||
|
|
||||||
|
|
||||||
@subsection[#:tag "sec:idmacro"]{Identifier Macros}
|
@subsection[#:tag "sec:idmacro"]{Identifier Macros}
|
||||||
|
|
||||||
|
@ -424,14 +402,14 @@ or: bad syntax
|
||||||
Identifier macros are allowed in both higher-order and top-level positions,
|
Identifier macros are allowed in both higher-order and top-level positions,
|
||||||
just like first-class functions.
|
just like first-class functions.
|
||||||
This lets us transparently alias built-in functions like @racket[regexp-match]
|
This lets us transparently alias built-in functions like @racket[regexp-match]
|
||||||
and @racket[vector-length] (see @Secref{sec:impl-trans}).
|
and @racket[vector-length] (see @Secref{sec:impl-elab}).
|
||||||
The higher-order uses cannot be checked for bugs, but they execute as normal
|
The higher-order uses cannot be checked for bugs, but they execute as normal
|
||||||
without raising new syntax errors.
|
without raising new syntax errors.
|
||||||
|
|
||||||
|
|
||||||
@subsection[#:tag "sec:def-implementation"]{Syntax Properties}
|
@subsection[#:tag "sec:def-implementation"]{Syntax Properties}
|
||||||
|
|
||||||
Syntax properties are the glue that let us chain transformations together.
|
Syntax properties are the glue that let us compose elaborations.
|
||||||
For instance, @racket[vector-map] preserves the length of its argument vector.
|
For instance, @racket[vector-map] preserves the length of its argument vector.
|
||||||
By tagging calls to @racket[vector-map] with a syntax property, our system
|
By tagging calls to @racket[vector-map] with a syntax property, our system
|
||||||
becomes aware of identities like:
|
becomes aware of identities like:
|
||||||
|
@ -452,21 +430,6 @@ This proved useful in our implementation of @racket[query-row], where we stored
|
||||||
|
|
||||||
Cooperating with @racket[let] and @racket[define] bindings is an important
|
Cooperating with @racket[let] and @racket[define] bindings is an important
|
||||||
usability concern.
|
usability concern.
|
||||||
When testing this library on existing code, we often saw code like:
|
|
||||||
|
|
||||||
@(begin
|
|
||||||
#reader scribble/comment-reader
|
|
||||||
(racketblock
|
|
||||||
(define rx-email #rx"^(.*)@(.*)\\.(.*)$")
|
|
||||||
|
|
||||||
;; Other code here
|
|
||||||
|
|
||||||
(define (get-recipient str)
|
|
||||||
(regexp-match rx-email str))
|
|
||||||
))
|
|
||||||
|
|
||||||
Similarly for database code and arithmetic constants.
|
|
||||||
|
|
||||||
To deal with @racket[let] bindings, we use a @racket[rename-transformer].
|
To deal with @racket[let] bindings, we use a @racket[rename-transformer].
|
||||||
Within the binding's scope, the transformer redirects references from a variable
|
Within the binding's scope, the transformer redirects references from a variable
|
||||||
to an arbitrary syntax object.
|
to an arbitrary syntax object.
|
||||||
|
@ -482,17 +445,18 @@ For our purposes, we redirect to an annotated version of the same variable:
|
||||||
]
|
]
|
||||||
|
|
||||||
For definitions, we use a @emph{free-identifier table}.
|
For definitions, we use a @emph{free-identifier table}.
|
||||||
This is less fancy--just a hashtable whose keys respect @exact{$\alpha$}-equivalence--but
|
This is less fancy--just a hashtable whose keys respect
|
||||||
still useful in practice.
|
@exact{$\alpha$}-equivalence--but still useful in practice.
|
||||||
|
|
||||||
|
|
||||||
@subsection[#:tag "sec:phase"]{Phasing}
|
@subsection[#:tag "sec:phase"]{Phasing}
|
||||||
|
|
||||||
Any code between a @racket[syntax-parse] pattern and the output syntax object
|
Any code between a @racket[syntax-parse] pattern and the output syntax object
|
||||||
is run at compile-time to generate the code that is ultimately run.
|
is run at compile-time to generate the code that is ultimately run.
|
||||||
In general terms, the code used to directly generate run-time code
|
In general terms, code used to directly generate run-time code
|
||||||
executes at @emph{phase level} 1 relative to the enclosing module.
|
executes at @emph{phase level} 1 relative to the enclosing module.
|
||||||
Code used to generate a syntax-parse pattern may be run at phase level 2, and
|
Code used to generate a @racket[syntax-parse] pattern may be run at
|
||||||
|
phase level 2, and
|
||||||
so on up to as many phases as needed@~cite[f-icfp-2002].
|
so on up to as many phases as needed@~cite[f-icfp-2002].
|
||||||
|
|
||||||
Phases are explicitly separated.
|
Phases are explicitly separated.
|
||||||
|
@ -501,11 +465,9 @@ Also by design, it is very easy to import bindings from any module at a specific
|
||||||
phase.
|
phase.
|
||||||
The upshot of this is that one can write and test ordinary, phase-0 Racket code
|
The upshot of this is that one can write and test ordinary, phase-0 Racket code
|
||||||
but then use it at a higher phase level.
|
but then use it at a higher phase level.
|
||||||
@; + non-meta programming
|
We also have functions like @racket[+] available at whatever stage of
|
||||||
@; + not getting stuck in ascending ladder
|
macro expansion we should need them---no need to copy and paste the implementation
|
||||||
@; + modular development
|
at a different phase level@~cite[ew-haskell-2012].
|
||||||
@; + don't need to worry about Singletons Haskell duplication
|
|
||||||
@; There is no need to duplicate code for use at different phases.
|
|
||||||
|
|
||||||
|
|
||||||
@subsection{Lexical Scope, Source Locations}
|
@subsection{Lexical Scope, Source Locations}
|
||||||
|
@ -513,7 +475,8 @@ The upshot of this is that one can write and test ordinary, phase-0 Racket code
|
||||||
Perhaps it goes without saying, but having macros that respect lexical scope
|
Perhaps it goes without saying, but having macros that respect lexical scope
|
||||||
is important for a good user and developer experience.
|
is important for a good user and developer experience.
|
||||||
Along the same lines, the ability to propogate source code locations in
|
Along the same lines, the ability to propogate source code locations in
|
||||||
transformations lets us report syntax errors in terms of the programmer's
|
elaborations lets us report syntax errors in terms of the programmer's
|
||||||
source code rather than locations inside our library.
|
source code rather than locations inside our library.
|
||||||
|
Even though we may implement complex transformations, errors can always be
|
||||||
|
traced to a source code line number.
|
||||||
|
|
||||||
|
|
|
@ -35,20 +35,19 @@ In this way, we handle binding forms and propagate information through
|
||||||
@item{
|
@item{
|
||||||
Interpretation and elaboration functions are defined over symbolic expressions
|
Interpretation and elaboration functions are defined over symbolic expressions
|
||||||
and values; specifically, over @emph{syntax objects}.
|
and values; specifically, over @emph{syntax objects}.
|
||||||
To make clear the difference between Typed Racket terms and representations
|
To distinguish terms and syntax objects representing
|
||||||
of terms, we quote the latter and typeset it in green.
|
terms, we quote the latter and typeset it in green.
|
||||||
Using this notation, @racket[(λ (x) x)] is a term implementing the identity
|
For example, @racket[(λ (x) x)] is a term implementing the identity
|
||||||
function and @racket['(λ (x) x)] is a representation that will evaluate
|
function and @racket['(λ (x) x)] is a representation that will evaluate
|
||||||
to the identity function.
|
to the identity function.
|
||||||
Values are typeset in green because their syntax and term representations are identical.
|
Values are typeset in @exact|{\RktVal{green}}| because their syntax and
|
||||||
|
term representations are identical.
|
||||||
} @item{
|
} @item{
|
||||||
In practice, syntax objects carry lexical information.
|
Syntax objects carry lexical information, but our examples treat them as
|
||||||
Such information is extremely important, especially for implementing @racket[define]
|
flat symbols.
|
||||||
and @racket[let] forms that respect @exact{$\alpha$}-equivalence, but
|
|
||||||
to simplify our presentation we omit it.
|
|
||||||
} @item{
|
} @item{
|
||||||
We use an infix @tt{:} to write explicit type annotations and casts,
|
We use an infix @tt{::} to write explicit type annotations and casts,
|
||||||
for instance @racket[(x : Integer)].
|
for instance @racket[(x :: Integer)].
|
||||||
These normally have two different syntaxes, respectively
|
These normally have two different syntaxes, respectively
|
||||||
@racket[(ann x Integer)] and @racket[(cast x Integer)].
|
@racket[(ann x Integer)] and @racket[(cast x Integer)].
|
||||||
}
|
}
|
||||||
|
@ -122,7 +121,7 @@ For all other syntax patterns, @racket[t-printf] is the identity elaboration.
|
||||||
|
|
||||||
\RktSym{{\Stttextmore}}\mbox{\hphantom{\Scribtexttt{x}}}\RktPn{(}\RktSym{t{-}printf}\mbox{\hphantom{\Scribtexttt{x}}}\RktVal{{\textquotesingle}}\RktVal{(}\RktVal{printf}\mbox{\hphantom{\Scribtexttt{x}}}\RktVal{"$\sim$b"}\mbox{\hphantom{\Scribtexttt{x}}}\RktVal{"2"}\RktVal{)}\RktPn{)}
|
\RktSym{{\Stttextmore}}\mbox{\hphantom{\Scribtexttt{x}}}\RktPn{(}\RktSym{t{-}printf}\mbox{\hphantom{\Scribtexttt{x}}}\RktVal{{\textquotesingle}}\RktVal{(}\RktVal{printf}\mbox{\hphantom{\Scribtexttt{x}}}\RktVal{"$\sim$b"}\mbox{\hphantom{\Scribtexttt{x}}}\RktVal{"2"}\RktVal{)}\RktPn{)}
|
||||||
|
|
||||||
\RktVal{{\textquotesingle}}\RktVal{(}\RktVal{printf}\mbox{\hphantom{\Scribtexttt{x}}}\RktVal{"$\sim$b"}\mbox{\hphantom{\Scribtexttt{x}}}\RktVal{(}\RktVal{"2"}\mbox{\hphantom{\Scribtexttt{x}}}\RktVal{{\hbox{\texttt{:}}}}\mbox{\hphantom{\Scribtexttt{x}}}\RktVal{Integer}\RktVal{)}\RktVal{)}
|
\RktVal{{\textquotesingle}}\RktVal{(}\RktVal{printf}\mbox{\hphantom{\Scribtexttt{x}}}\RktVal{"$\sim$b"}\mbox{\hphantom{\Scribtexttt{x}}}\RktVal{(}\RktVal{"2"}\mbox{\hphantom{\Scribtexttt{x}}}\RktVal{{\hbox{\texttt{::}}}}\mbox{\hphantom{\Scribtexttt{x}}}\RktVal{Integer}\RktVal{)}\RktVal{)}
|
||||||
|
|
||||||
\RktSym{{\Stttextmore}}\mbox{\hphantom{\Scribtexttt{x}}}\RktPn{(}\RktSym{t{-}printf}\mbox{\hphantom{\Scribtexttt{x}}}\RktSym{printf}\RktPn{)}
|
\RktSym{{\Stttextmore}}\mbox{\hphantom{\Scribtexttt{x}}}\RktPn{(}\RktSym{t{-}printf}\mbox{\hphantom{\Scribtexttt{x}}}\RktSym{printf}\RktPn{)}
|
||||||
|
|
||||||
|
@ -219,8 +218,8 @@ It also raises syntax errors when an uncompiled regular expression contains
|
||||||
|
|
||||||
@racketblock[
|
@racketblock[
|
||||||
> (t-regexp '(regexp-match #rx"(a)b" str))
|
> (t-regexp '(regexp-match #rx"(a)b" str))
|
||||||
'(cast (regexp-match #rx"(a)b" str)
|
'((regexp-match #rx"(a)b" str)
|
||||||
(U #f (List String String)))
|
:: (U #f (List String String)))
|
||||||
> (t-regexp '(regexp-match "(" str))
|
> (t-regexp '(regexp-match "(" str))
|
||||||
⊥
|
⊥
|
||||||
]
|
]
|
||||||
|
@ -377,7 +376,7 @@ Arguments substituted for query parameters are guarded against SQL injection.
|
||||||
> (query-row C
|
> (query-row C
|
||||||
"SELECT plaintiff FROM rulings WHERE name = '$1' LIMIT 1"
|
"SELECT plaintiff FROM rulings WHERE name = '$1' LIMIT 1"
|
||||||
2001)
|
2001)
|
||||||
#("Kyllo")
|
'#("Kyllo")
|
||||||
]
|
]
|
||||||
|
|
||||||
This is a far cry from language-integrated query@~cite[mbb-sigmod-2006] or
|
This is a far cry from language-integrated query@~cite[mbb-sigmod-2006] or
|
||||||
|
@ -465,17 +464,17 @@ There is still a non-trivial amount of work to be done resolving wildcards and
|
||||||
> (t '(query-row C
|
> (t '(query-row C
|
||||||
"SELECT plaintiff FROM decisions WHERE year = '$1' LIMIT 1"
|
"SELECT plaintiff FROM decisions WHERE year = '$1' LIMIT 1"
|
||||||
2006))
|
2006))
|
||||||
'(cast (query-row C
|
'((query-row C
|
||||||
"SELECT ..."
|
"SELECT ..."
|
||||||
(2006 : Natural))
|
(2006 : Natural))
|
||||||
(Vector String))
|
:: (Vector String))
|
||||||
> (t '(query-row C
|
> (t '(query-row C
|
||||||
"SELECT * FROM decisions WHERE plaintiff = '$1' LIMIT 1"
|
"SELECT * FROM decisions WHERE plaintiff = '$1' LIMIT 1"
|
||||||
"United States"))
|
"United States"))
|
||||||
'(cast (query-row C
|
'((query-row C
|
||||||
"SELECT ..."
|
"SELECT ..."
|
||||||
("United States" : String))
|
("United States" : String))
|
||||||
(Vector Natural String String Natural))
|
:: (Vector Natural String String Natural))
|
||||||
> (t '(query-row C "SELECT foo FROM decisions"))
|
> (t '(query-row C "SELECT foo FROM decisions"))
|
||||||
⊥
|
⊥
|
||||||
]
|
]
|
||||||
|
@ -483,7 +482,8 @@ There is still a non-trivial amount of work to be done resolving wildcards and
|
||||||
Results produced by @racket[query-row] are vectors with a known length;
|
Results produced by @racket[query-row] are vectors with a known length;
|
||||||
as such they cooperate with our library of vector operations described in
|
as such they cooperate with our library of vector operations described in
|
||||||
@Secref{sec:vector}.
|
@Secref{sec:vector}.
|
||||||
All these compose seamlessly, without any burden on the user.
|
Accessing a constant index into a row vector is statically guaranteed
|
||||||
|
to be in bounds.
|
||||||
|
|
||||||
@; casts can fail, especially if you wildcard without saying all the rows
|
@; casts can fail, especially if you wildcard without saying all the rows
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user