Lang Lib
Lang Lib
Sam Tobin-Hochstadt Vincent St-Amour Ryan Culpepper Matthew Flatt Matthias Felleisen
Northeastern University Northeastern University University of Utah University of Utah Northeastern University
Abstract collectors and thread abstractions, that these platforms offer. Both
Programming language design benefits from constructs for extend- platforms also inspired language design projects that wanted to ex-
ing the syntax and semantics of a host language. While C’s string- periment with new paradigms and to exploit existing frameworks;
based macros empower programmers to introduce notational short- thus Clojure, a parallelism-oriented descendant of Lisp, and Scala,
hands, the parser-level macros of Lisp encourage experimentation a multi-paradigm relative of Java, target the JVM, while F# is built
with domain-specific languages. The Scheme programming lan- atop .NET. In all of these cases, however, the platform is only a
guage improves on Lisp with macros that respect lexical scope. target, not a tool for growing languages. As a result, design experi-
The design of Racket—a descendant of Scheme—goes even fur- ments on these platforms remain costly, labor-intensive projects.
ther with the introduction of a full-fledged interface to the static se- To follow Steele’s advice on growing a language (1998) re-
mantics of the language. A Racket extension programmer can thus quires more than a reusable virtual machine and its libraries;
add constructs that are indistinguishable from “native” notation, it demands an extensible host language that supports linguistic
large and complex embedded domain-specific languages, and even reuse (Krishnamurthi 2001). Thus, a derived language should be
optimizing transformations for the compiler backend. This power able to reuse the scoping mechanisms of the host language. Simi-
to experiment with language design has been used to create a series larly, if the host language offers namespace management for iden-
of sub-languages for programming with first-class classes and mod- tifiers, a language designer should have the freedom to lift that
ules, numerous languages for implementing the Racket system, and management into an experimental language.
the creation of a complete and fully integrated typed sister language Providing such freedoms to designers demands a range of ex-
to Racket’s untyped base language. tension mechanisms. A programmer may wish to manipulate the
This paper explains Racket’s language extension API via an im- surface syntax and the AST, interpose a new context-sensitive static
plementation of a small typed sister language. The new language semantics, and communicate the results of the static semantics to
provides a rich type system that accommodates the idioms of un- the backend. Best of all, this kind of work can proceed without any
typed Racket. Furthermore, modules in this typed language can changes to the host language, so that the resulting design is essen-
safely exchange values with untyped modules. Last but not least, tially a library that supplements the existing compiler.
the implementation includes a type-based optimizer that achieves In this paper, we present the Racket1 (Flatt and PLT 2010) plat-
promising speedups. Although these extensions are complex, their form, which combines a virtual machine and JIT compiler with
Racket implementation is just a library, like any other library, re- a programming language that supports extension mechanisms for
quiring no changes to the Racket implementation. all phases of language implementation. With Racket’s arsenal of
extension mechanisms, a programmer may change all aspects of
Categories and Subject Descriptors D.3.3 [Programming Lan- a language: lexicographic and parsed notation, the static seman-
guages]: Language Constructs and Features tics, module linking, and optimizations. The Racket development
team has exploited this extensibility for the construction of two
General Terms Languages, Design
dozen frequently used languages, e.g., language extensions for
classes (Flatt et al. 2006) and ML-style functors (Culpepper et
1. Growing Many Languages al. 2005; Flatt and Felleisen 1998), another for creating interac-
I need to design a language that can grow. — Guy Steele, 1998 tive web server applications (Krishnamurthi et al. 2007), two lan-
guages implementing logic programming2 and a lazy variant of
Virtual machines inspire language experimentation. For example, Racket (Barzilay and Clements 2005). This paper is itself a pro-
the Java Virtual Machine and the .NET CLR attracted many im- gram in Racket’s documentation language (Flatt et al. 2009).
plementors to port existing languages. Their goal was to benefit Racket derives its power from a carefully designed revision of a
from the rich set of libraries and runtime facilities, such as garbage Scheme-style macro systems. The key is to make language choice
specific to each module. That is, individual modules of a program
∗A preliminary version of this work appeared at the Workshop on Scheme can be implemented in different languages, where the language
and Functional Programming (Culpepper et al. 2007). This work has been implementation has complete control over the syntax and semantics
supported by the DARPA, Mozilla Foundation, NSERC, and NSF. of the module. The language implementation can reuse as much
of the base language as desired, and it can export as much of the
module’s internals as desired. In particular, languages can reuse
Racket’s macro facilities, which is supported with a mechanism for
Permission to make digital or hard copies of all or part of this work for personal or locally expanding macros into attributed ASTs.
classroom use is granted without fee provided that copies are not made or distributed
for profit or commercial advantage and that copies bear this notice and the full citation
on the first page. To copy otherwise, to republish, to post on servers or to redistribute
1 Formerly known as PLT Scheme.
to lists, requires prior specific permission and/or a fee.
PLDI’11, June 4–8, 2011, San Jose, California, USA. 2 Schelog (1993) http://docs.racket-lang.org/racklog
Copyright © 2011 ACM 978-1-4503-0663-8/11/06. . . $5.00 Datalog (2010) http://docs.racket-lang.org/datalog
For simplicity, we demonstrate the idea of true language ex- The do-10-times macro consumes a sequence of expressions
tensibility via a single example: the Typed Racket implementation. and produces code that runs these expressions 10 times. To this
Typed Racket (Tobin-Hochstadt and Felleisen 2008) is a statically end, it first decomposes its input, stx, with the syntax-parse
typed sister language of Racket that is designed to support the grad- form (Culpepper and Felleisen 2010), a pattern matcher designed
ual porting of untyped programs. Its type system accommodates the for implementing macros. The pattern requires a sequence of
idioms of Racket, its modules can exchange values with untyped subexpressions, indicated with ..., named body, each of which
modules, and it has a type-driven optimizer—all implemented as is constrained to be an expr. The resulting syntax is a for loop
plain Racket libraries. that contains the body expressions. Thanks to macro hygiene, if
The remainder of this paper starts with a description of Racket’s the bodys use the variable i, it is not interfered with by the use of
language extension facilities and the challenges posed by the Typed i in the for loop.
Racket extension. Following these background sections, the paper
shows how we use syntactic extension to introduce Typed Racket 2.2 Manipulating Syntax Objects
notation; how we splice the type checker into the tool chain; how Since syntax objects are Racket’s primary compile-time data struc-
we enable inter-language linking; and how we add realistic op- ture, roughly analagous to the ASTs of conventional languages,
timizing transformations via a library. Finally, we relate our ap- syntax objects come with a rich API.
proach to existing facilities for meta-programming with static se-
mantics as well as extensible compiler projects. Constructors and Accessors Besides with-syntax and #’ for
constructing and manipulating syntax objects, the remainder of the
paper also uses the following forms and functions:
2. Language Extension in Racket
• syntax->list converts a non-atomic syntax object to a list;
Racket provides a range of facilities for language extension. In
• free-identifier=? compares two identifiers to deter-
this section, we outline the most relevant background as well as
extension mechanisms provided with Racket. mine if they refer to the same binding;
• the #‘ and #, forms implement Lisp’s quasiquote and unquote
2.1 Macros for syntax object construction.
From Lisp (Steele Jr. 1994) and Scheme (Sperber et al. 2009), Syntax properties Racket macros can add out-of-band informa-
Racket takes hygienic macros as the basis of its syntactic exten- tion to syntax objects, e.g., source locations, dubbed syntax proper-
sion mechanisms. In Racket, macros are functions from syntax to ties. The syntax-property-put and syntax-property-
syntax, which are executed at compile time. During compilation, get procedures attach and retrieve arbitrary key-value pairs on
Racket’s macro expander recursively traverses the input syntax. syntax objects, which are preserved by the macro expander. Thus
When it reaches the use of a macro, it runs the associated function, syntactic extensions may communicate with each other without in-
the transformer, and continues by traversing the result. terfering with each other.
Macros may use arbitrary Racket libraries and primitives to
compute the result syntax. The following macro queries the system Local Expansion The local-expand procedure expands a
clock at compile time: syntax object to core Racket. This explicitly specified core lan-
(define-syntax (when-compiled stx) guage consists of approximately 20 primitive syntactic forms that
(with-syntax ([ct (current-seconds)]) implement Racket; see figure 1 for a subset of this grammar. Using
#’ct)) local-expand, a language extension may analyze an arbitrary
This macro computes, at compile time, the current time in seconds, expression, even if that expression uses macros. For example, the
and uses the with-syntax form to bind the identifier ct to following macro requires that its argument be a λ expression:
a syntax object representing this number. Syntax objects are the (define-syntax (only-λ stx)
ASTs of Racket, and contain syntactic data as well as metadata (syntax-parse stx
such as source location information. The #’ form (analogous to ’ [(_ arg:expr)
for lists) constructs syntax objects and can refer to identifiers bound (define c
by with-syntax in its context. In the following function, we use (local-expand #’arg ’expression ’()))
the when-compiled macro: (define k (first (syntax->list c)))
(define (how-long-ago?) (if (free-identifier=? #’#%plain-lambda k)
(- (current-seconds) (when-compiled))) c
Since the when-compiled form expands into the current date (error "not λ"))]))
in seconds at the time the form is compiled and since the value of The only-λ macro uses local-expand on the arg subexpres-
(current-seconds) continues to change, the value produced sion to fully expand it:
by how-long-ago? continually increases > (only-λ (λ (x) x))
> (how-long-ago?) #<procedure>
0 > (only-λ 7)
> (sleep 1) not λ
> (how-long-ago?) If we add a definition that makes function the same as λ, we
1 still get the correct behavior.
Most macros generate new expressions based on their input: > (only-λ (function (x) x))
(define-syntax (do-10-times stx) #<procedure>
(syntax-parse stx The only-λ macro can see through the use of function be-
[(do-10-times body:expr ...) cause of the use of local-expand.
#’(for ([i (in-range 10)])
body ...)])) 2.3 Modules and Languages
> (do-10-times (display "*") (display "#")) Racket provides a first-order module system that supports import
*#*#*#*#*#*#*#*#*#*# and export of language extensions (Flatt 2002). For the purposes
mod-form = expr 3. Typed Racket as a Library
| (#%provide provide-spec)
Typed Racket combines a type system for enriching Racket mod-
| (define-values (id) expr)
ules with sound type information and a mechanism for linking
| ...
typed and untyped modules. Its implementation not only benefits
from, but demands, as much linguistic reuse as possible. After all,
expr = id
Typed Racket must implement the same semantics as Racket plus
| (#%plain-lambda (id ...)
purely syntactic type checking; there is no other way to provide
expr ...+)
a smooth migration path that turns Racket programs into Typed
| (if expr expr expr)
Racket programs on a module-by-module basis. And the best way
| (quote datum)
to implement the same semantics is to share the compiler.
| (#%plain-app expr ...+)
At the same time, linguistic reuse poses novel challenges in
| ...
addition to those faced by every implementer of a typed language.
In this section, we explain these challenges with examples; in
Figure 1: Racket’s core forms (abbreviated) the remainder of the paper we explain our solutions. As for the
particular challenges, we show how to:
small fixed set of core forms of figure 1, and reduce all other forms fiers that the language provides, such as +, as well as the initial
to these before type checking. type names. Finally, we provide the define and λ binding forms
Given an implementation of the typechecker, we must connect it that attach the appropriate syntax properties for type annotations to
to the program so that it receives appropriate fully-expanded syntax the bound variables, as described in section 2.1.
objects as input. The basic driver is given in figure 2. The driver
is straightforward, performing only two functions. Once we have 4.3 Typechecking Syntax
fully expanded the body of the module, we typecheck each form in
turn. The typechecker raises an error if it encounters an untypable Figure 1 displays the grammar for our simple language. It is a sub-
form. Finally, we construct the output module from new core forms, set of the core forms of full Racket. Modules consist of a sequence
thus avoiding a re-expansion of the input. of mod-forms, which are either expressions or definitions.
The strategy of reducing syntactic sugar to core forms is com- Figure 3 specifies the typechecker for this core language. The
mon in many other languages, and even specified in the standards typecheck function takes a term and an optional result type.
for ML (Milner et al. 1997) and Haskell (Marlow 2010). In a lan- Each clause in the syntax-parse expression considers one of
guage with syntactic extension, we need more sophisticated sup- the core forms described in figure 1.
port from the system to implement this strategy, and that support is Two aspects of the typechecker are distinctive. First, the type en-
provided in Racket by local-expand. vironment uses a mutable table mapping identifiers to types based
For successful typechecking, we must also provide an initial en- on their binding; the table is accessed with lookup-type and up-
vironment. The initial environment specifies types for any identi- dated with add-type!. Shadowing is impossible because identi-
fiers in fully-expanded Racket programs are unique with respect to
the entire program. We update the type environment in the clauses (define-syntax (require/typed stx)
for both #%plain-lambda and define-values. Using an (syntax-parse stx
identifier-keyed table allows reuse of the Racket binding structure [(_ module [id ty])
without having to reimplement variable renaming or environments. #‘(begin-ignored
The second distinctive feature of the typechecker is the type- ; Stage 1
of function. It reads the syntax properties attached by forms such (require (only-in module
as define: in section 3.1 with a known key to determine the type [id unsafe-id]))
the user has added to each binding position. ; Stage 2
(begin-for-syntax
(add-type! #’id (parse-type #’ty)))
4.4 Scaling to Typed Racket ; Stage 3
While this module-level typechecker is simple, the full implemen- (define id
tation for Typed Racket employs the same strategy. The impor- (contract
tant differences concern mutual recursion and complex definition #,(type->contract
forms. Mutual recursion is implemented with a two-pass type- (parse-type #’ty))
checker: the first pass collects definitions with their types, and unsafe-id
the second pass checks individual expressions in this type context. (quote module)
Complex declarations, such as the definition of new types, are also ’typed-module)))]))
handled in the first pass. They are recognized by the typechecker,
and the appropriate bindings and types are added to the relevant Figure 4: Import of Untyped Code
environments.
Of course, the Typed Racket type system is much more com-
plex (Strickland et al. 2009; Tobin-Hochstadt and Felleisen 2010) 6. Safe Cross-Module Integration
than the one we have implemented here, but that complexity does A module in Typed Racket should have access to the large col-
not require modifications to the structure of the implementation—it lection of untyped libraries in Racket. Conversely, our intention
is encapsulated in the behavior of typecheck on the core forms. to support gradual refactoring of untyped into typed systems de-
mands that typed modules can export bindings to untyped mod-
ules. However, untyped programs are potentially dangerous to the
5. Modular Typed Programs invariants of typed modules. To protect these invariants, we auto-
The typechecker in section 4 deals only with individual modules. matically generate run-time contracts from the types of imported
To deal with multiple modules, we must both propagate type infor- and exported bindings (Tobin-Hochstadt and Felleisen 2006).
mation between typed modules, and also persist type information However, a large library of untyped modules is useless if each
in compiled code to support separate compilation. must be modified to work with typed modules. Therefore, our im-
For the first point, we reuse some Racket infrastruture. Names- plementation must automatically wrap imports from untyped code,
pace management in a modular language is a complex problem, and and protect exports to untyped code, without requiring changes to
one that Racket already solves. In particular, identifiers in Racket untyped modules. Further, communication between typed modules
are given globally fresh names that are stable across modules dur- should not involve extra contract checks, since these invariants are
ing the expansion process. Since our type environment is keyed by enforced statically.
identifiers, type environment lookup reuses and respects Racket’s
scoping. An identifier imported from one module maintains its 6.1 Imports from Untyped Modules
identity in the importing module, and therefore the typechecker is Typed Racket requires the programmer to specify the types of
able to look up the appropriate type for the binding. imports from untyped modules:
The second step is to maintain the type environment across com- (require/typed file/md5
pilations. Since each module is compiled in a separate and fresh [md5 (Bytes -> Bytes)])
store, mutations to the type environment do not persist between This specification imports the md5 procedure, which computes the
compilations. Therefore, Typed Racket must incorporate the type MD5 hash of a byte sequence. The procedure can be used in typed
environment into the residual program, because that is the only per- code with the specified type, and the type is converted to a con-
sistent result of compilation. Our strategy — due to Flatt (2002) — tract and attached to the procedure on import. This translation and
is to include code in the resulting module that populates the type interposition is implemented in the require/typed form; see
environment every time the module is required. figure 4 for an implementation for our simple-type language.
In our example system, we implement this by adding a single The implementation of require/typed works in three
rewriting pass to the #%module-begin form for Typed Racket. stages. Stage 1 imports the specified identifier, id from mod-
Expressions and definitions are left alone; an appropriate compile- ule under the new name unsafe-id. Stage 2 parses and adds
time declaration is added for each export: the specified type to the table of types with add-type!. Finally,
(provide n) ; The original export stage 3 defines the identifier id as a wrapper around unsafe-id.
; ==> (is rewritten into) The wrapper is a generated contract and establishes a dynamically
(with-syntax ([t (serialize (type-of #’n))]) enforced agreement between the original module (named module)
#‘(begin and the typed module (with the placeholder name).3 The entire out-
(#%provide n) ; The core export form put is wrapped in begin-ignored so that the type checker does
(begin-for-syntax not process this meta-information.
(add-type! #’n t)))) In our example, we would now be able to use the md5 function
The resulting code uses #%provide to maintain the original ex- in typed code according to the specified type, getting a static type
port. The type declaration is wrapped in begin-for-syntax, error if md5 is applied to a number, for example. Conversely, if the
meaning that it is executed at compile time to declare that n is
mapped to the serialization of its type in the type environment. 3 In practice, type checking renders the domain contract superfluous.
file/md5 library fails to return a byte string value, a dynamic as an indicator, without the possibility that untyped code might be
contract error is produced, avoiding the possibility that the typed able to deceive the typechecker.
module might end up with a value that it did not expect.
6.3 Scaling to Typed Racket
6.2 Exports to Untyped Modules The full implementation of module integration in Typed Racket
follows the strategy outlined. The major complication is the export
Unlike imports into a typed module, exports from such a module of macros and other static information from typed modules. Since
pose a serious problem because the Typed Racket language imple- macros from typed modules can refer to internal identifiers not
mentation is not necessarily in control of the use site. After all, an protected by contracts, expanding such macros in untyped modules
exported identifier may be used in both typed and untyped contexts. could potentially allow untyped modules to violate the invariants of
Since typed modules statically verify that uses of typed identifiers typed modules. Therefore, Typed Racket currently prevents macros
accord with their types, no contracts are necessary for exports to defined in typed modules from escaping into untyped modules.
such modules. In contrast, exports from typed to untyped modules
require the insertion of dynamic checks to ensure type safety.
To implement this behavior without cloning every module, we
7. Optimization via Rewriting
adopt a novel two-stage module compilation strategy. First, each Source-to-source transformations can express large classes of opti-
export is replaced with an indirection that chooses whether to ref- mizations, which makes it possible to apply them in the front end
erence the contracted or plain version of the exported binding. of the compiler. With the right language extension mechanisms, we
Second, the compilation of typed modules sets a flag inside the can express these transformations as libraries and use them to build
#%module-begin transformer before expansion of the module’s competitive optimizers.
contents; the exported indirections choose which version to ref-
erence based on this flag. Since each module is compiled with a 7.1 Compiler architecture
fresh state, this flag is only set during the compilation of typed Since Typed Racket is built as a language extension of Racket and
modules—untyped modules have no way to access it. Therefore, the Racket compiler is for an untyped language, Typed Racket
during the compilation of untyped modules, the export indirections has to apply typed optimizations before handing programs to the
correctly choose the contract-protected version of bindings. The Racket compiler. In contrast, compilers for typed languages can
compilation of typed modules, in contrast, see the set version of keep track of types across all phases in the compiler and use
the flag and are able to use the uncontracted versions of bindings. them to guide optimizations. Hence, Typed Racket features a type-
driven optimization pass after typechecking. This optimization pass
Implementation Exported identifiers are rewritten in the same transforms the code that the front end of Typed Racket generates,
stages as imported identifiers. Since this rewriting occurs after using the validated and still accessible type information.
typechecking, however, it is performed by the #%module-begin To support realistic optimizers as libraries, the host language
form, just as declarations are added to exports: must provide ways for language extensions to communicate their
(#%provide n) ; An export of n results to the compiler’s backend. As part of its language exten-
; ==> (is rewritten to) sion features, Racket exposes unsafe type-specialized primitives.4
#‘(begin For instance, the unsafe-fl+ primitive adds two floating-point
... the declaration from section 5 ... numbers, but has undefined behavior when applied to anything
; Stage 1 else. These type-specialized primitives are more efficient than their
(define defensive-n generic equivalents; not only do these primitives avoid the run-
(contract time dispatch of generic operations, they also serve as signals to
#,(type->contract (typecheck #’n)) the Racket code generator to guide its unboxing optimizations.
n ’typed-module ’untyped-module)) Typed Racket’s optimizer generates code that uses these prim-
; Stage 2 itives. In figure 5, we show an excerpt from the optimizer that
(define-syntax (export-n stx) specializes floating-point operations using rewrite rules; the opti-
(if (unbox typed-context?) mizer rewrites uses of generic arithmetic operations on floating-
#’n #’defensive-n)) point numbers to specialized operations.
; Stage 3
(provide (rename-out [export-n n]))) 7.2 Scaling to Typed Racket
First, we define a defensive version of the identifier n, which Typed Racket uses the same techniques as the simple optimizer pre-
uses a contract generated from the type of n. Second, we de- sented here, but applies a wider range of optimizations. It supports a
fine an export-n version of the identifier n, which selects be- number of floating-point specialization transformations, eliminates
tween n and defensive-n depenending on the value of typed- tag-checking made redundant by the typechecker and performs ar-
context?. Third, we provide export-n under the name n, ity raising on functions with complex number arguments.
making the indirection transparent to clients of the typed module.
The second part comes in the definition of the language: 7.3 Results
(define-syntax (#%module-begin stx) Untyped Racket is already competitive among optimizing Scheme
(set-box! typed-context? #t) compilers. The addition of a type-driven optimizer makes a notice-
... check and transform stx, as in figure 2 ... able difference and makes it an even more serious contender.
... rewrite provides, as above ...) We show the impact of our optimizer on micro-benchmarks
The initial flag setting means that the expansion of the module, and taken from the Gabriel (1985) and Larceny (Clinger and Hansen
in particular the expansion of the indirections imported from other 1994) benchmark suites and the Computer Language Benchmark
typed modules, see the typed-context? flag as set to #t. Game,5 as well as on large benchmarks: the pseudoknot (Hartel
Finally, because the typed-context? flag is accessible only
4 Initially
these primitives were provided because some programmers
from the implementation of the simple-type language, it is
simple to verify that the flag is only set to #t in the #%module- wanted to hand-optimize code.
begin form. Therefore, the implementation can rely on this flag 5 http://shootout.alioth.debian.org
(define (optimize t)
(syntax-parse t
[(#%plain-app op:id e1:expr e2:expr)
(with-syntax ([new-op (if (and (equal? FloatT (type-of #’e1))
(equal? FloatT (type-of #’e2)))
(cond [(free-identifier=? #’op #’+) #’unsafe-fl+]
[(free-identifier=? #’op #’-) #’unsafe-fl-]
[else #’op])
#’op)])
#‘(#%plain-app new-op #,(optimize #’e1) #,(optimize #’e2)))]
... structurally recur on the other forms ...))
2.5
Normalized time
2
Racket
1.5 Typed Racket
Bigloo
1
Larceny
0.5 Gambit
0
cpstack dderiv deriv div fft graphs lattice maze mazefun nfa nqueens paraffins tak takl triangle
1
structures (Prashanth and Tobin-Hochstadt 2010). Each benchmark
0.5 Racket comes in two versions: the original version and a translation to
Typed Typed Racket. The typed versions have type annotations and extra
0 Racket predicates where required to typecheck the program.
nbody
spectralnorm
fannkuch
fasta
k-nucleotide
regex-dna
mandelbrot
rev.comp.
binarytrees
pidigits
The typed version runs in Racket 5.0.2 using our type-driven op-
timizer and the untyped version in Racket 5.0.2, Gambit 4.6.0 (Fee-
ley and Miller 1990), Larceny 0.97 (Clinger and Hansen 1994)
and Bigloo 3.5a (Serrano and Weis 1995), using the highest safe
optimization settings in each case. Benchmarks from the Com-
Figure 7: Results on the Computer Language Benchmark Game puter Language Benchmarks Game and our sample applications
(smaller is better) use Racket-specific features and cannot be measured with other
Scheme compilers. Bigloo fails to compile the cpstack and pseu-
1.5
Normalized time
1.5
Racket compiler and runtime are already competitive with leading
1 Racket compilers for Scheme, and (b) that Typed Racket’s optimizer, writ-
Typed ten entirely as a library, nonetheless provides noticeable speedups
0.5
Racket on both widely-used benchmarks and existing Racket programs.
0
ray tracer fft banker's queue leftist heap