1 Language Overview

The Revised⁶ Report on the Algorithmic Language Scheme (R⁶RS) is the sixth formal revision of the Scheme programming language standard, published in 2007 by the Scheme Steering Committee. It introduces a standardized module system, a new library organization, and several language enhancements while maintaining compatibility with previous reports. R⁶RS aims to provide a more robust foundation for practical programming without sacrificing the minimalist elegance of Scheme.

1.1 Notation and Terminology

R⁶RS uses a formal notation for describing syntax and semantics. Metavariables are written in italic, and syntactic categories are indicated by angle brackets. The report employs the term "unspecified" to denote situations where the language does not prescribe a particular behavior, and "an error" to indicate that implementations can report an error or behave arbitrarily. Rule specifications use a context-free grammar augmented with semantic predicates.

1.2 Syntactic Conventions

1.2.1 Identifiers and Keywords

Identifiers in R⁶RS may consist of letters, digits, and certain special characters, with the restriction that they cannot start with a character that could begin a number. Keywords are identifiers used in syntactic forms; they are not variables. The report defines a standard set of keywords for built-in forms such as if, lambda, and define. Implementations may extend this set, but portable programs should avoid conflicting with standard keywords.

1.2.2 Datum Comments and Whitespace

R⁶RS introduces datum comments, written as #; followed by a datum. The datum and the comment marker are treated as whitespace and ignored by the reader. Whitespace characters include spaces, tabs, newlines, and form feeds. Comments can also be written with a semicolon (;) which extends to the end of the line.

1.3 Backward Compatibility with R⁵RS

R⁶RS is designed to be backward compatible with the previous standard, R⁵RS, in the sense that R⁵RS programs can be adapted to run under R⁶RS with minimal changes. The main incompatibilities arise from the introduction of libraries and the deprecation of certain features. A compatibility library (rnrs r5rs) is provided to support R⁵RS programs directly.

2 Libraries and Top-Level Programs

2.1 Library Syntax

A library is a named collection of definitions and exports. The syntax for defining a library is:

(library <library-name> (export <export-spec> ...) (import <import-spec> ...) <body>)

2.1.1 Library Declarations

Library declarations appear within the library body. They include define, define-syntax, and cond-expand. Declarations can also include begin for side effects and include to import source files. Libraries may contain multiple declarations, evaluated in order.

2.1.2 Import and Export Specifications

Import specifications use the (import ...) form and can specify whole libraries or selective imports via subforms such as only, except, prefix, and rename. Export specifications list the identifiers that the library makes available to importers. A library can re-export identifiers from other libraries.

2.2 Top-Level Program Structure

A top-level program is a sequence of definitions and expressions, optionally preceded by import declarations. Programs are executed in an environment that includes the imported bindings. The program starts at its first expression or definition after the imports.

2.3 Standard Library Organization

The R⁶RS standard libraries are organized hierarchically under the (rnrs ...) prefix. They are divided into primitive and compound libraries.

2.3.1 Primitive Libraries

Primitive libraries provide low-level functionality. Examples include (rnrs base), (rnrs arithmetic) and (rnrs hashtables). Each primitive library defines a minimal set of bindings without relying on other libraries.

2.3.2 Compound Libraries

Compound libraries combine multiple primitive libraries under a single name, such as (rnrs), which exports all bindings from all standard libraries except the compatibility libraries. Compound libraries simplify importing a broad set of functionality.

3 Primitive Expressions

Primitive expressions are the core syntactic forms from which all other expressions are built.

3.1 Literal Expressions

Literal expressions evaluate to themselves. They include numbers, characters, strings, booleans, the empty list, and vectors prefixed with #(...). The literal syntax for symbols uses the quote form: 'symbol. Datum comments and vector literals are also part of this category.

3.2 Variable References

A variable reference is an expression that evaluates to the value bound to a variable. It is simply an identifier that is not a syntactic keyword. The variable must be in scope, either from a local binding or from an imported library.

3.3 Procedure Calls

A procedure call is written as (operator operand ...), where operator evaluates to a procedure and the operands are evaluated in left-to-right order. The procedure is then applied to the resulting argument list.

3.4 Conditionals

Conditionals allow selection among alternative expressions based on a test.

3.4.1 if, cond, and case

  • if: (if test consequent alternative) evaluates test; if true, evaluates and returns consequent, otherwise evaluates alternative. The alternative can be omitted, defaulting to unspecified.
  • cond: (cond (test expr ...) ... (else expr ...)) provides multi-way branching. Each test is evaluated in order; if true, the corresponding expressions are evaluated and the last value returned. An else clause is optional.
  • case: (case key ((datum ...) expr ...) ...) compares key to the datums using eqv?; the first matching clause is executed.

3.4.2 when and unless

when and unless are conditional forms that provide a side-effect-oriented structure. (when test expr ...) evaluates test; if true, evaluates all expr in order and returns the last value. unless is the complement, evaluating expressions only when the test is false.

3.5 Assignment and Binding

3.5.1 define, set!

define introduces a new variable in the current scope. At top level or in a library, (define var expr) binds var to the value of expr. In internal definitions, define can appear within a body. set! assigns a new value to an existing variable: (set! var expr).

3.5.2 let, let*, letrec, letrec*

  • let: (let ((var expr) ...) body) creates a new scope where each variable is bound to the result of the corresponding expression; expressions are evaluated outside the new bindings.
  • let*: Similar to let, but expressions are evaluated sequentially, allowing later bindings to refer to earlier ones.
  • letrec: Allows mutual recursion; all expressions are evaluated in the scope of all bindings.
  • letrec*: Like letrec but with sequential evaluation, supporting dependencies in initialization.

4 Derived Expression Types

Derived expressions are syntactic forms that can be expressed in terms of primitive expressions.

4.1 Derived Conditionals

R⁶RS provides derived conditional forms such as and and or, which are defined via if. (and expr ...) returns the last expression if all are true, otherwise #f. (or expr ...) returns the first true value, otherwise #f.

4.2 Derived Bindings

Derived binding forms include fluid-let (not in standard, but often implemented) and let-values for binding multiple values. let-values uses syntax (let-values ((formals expr) ...) body) where formals is a list of variables to receive the values returned by expr.

4.3 Iteration and Sequencing

4.3.1 do, named let

  • do: (do ((var init step) ...) (test result ...) expr ...) provides a loop with variables updated each iteration. The loop terminates when test is true.
  • Named let: (let name ((var init) ...) body) creates a tail-recursive loop. The body can call name with new arguments.

4.3.2 begin

begin sequences expressions for side effects: (begin expr ...). Each expression is evaluated in order, and the value of the last expression is returned. begin is used within definition bodies and elsewhere.

5 Program Units and Macros

5.1 Syntactic Abbreviations

Syntactic abbreviations allow defining new syntactic forms using existing ones. R⁶RS provides syntax-rules for pattern-based macros and syntax-case for more powerful procedural macros.

5.2 Transformers

5.2.1 Pattern-Based Macros

Pattern-based macros are defined with define-syntax and syntax-rules. The macro matches an input pattern against a set of templates, rewriting the expression. Example: (define-syntax my-let (syntax-rules () ((my-let ((var val) ...) body) ((lambda (var ...) body) val ...)))).

5.2.2 Procedural Macros

Procedural macros are defined using syntax-case, which provides access to the syntactic representation of input. The transformer can inspect and construct syntax objects using the syntax and quasisyntax forms. This allows macros that depend on the environment or that perform complex transformations.

5.3 Hygiene and Transparency

5.3.1 Lexical Scope

Macros are hygienic by default: they respect lexical scope and prevent accidental capture of identifiers. This ensures that macro expansions do not introduce unintended bindings.

5.3.2 Generated Identifiers

Procedural macros can generate new identifiers using the syntax form with datum->syntax. Such identifiers are unique and respect the lexical context. This is useful for creating gensym-like symbols to avoid name clashes.

6 Standard Procedures

6.1 Booleans and Equivalence Predicates

The boolean objects #t and #f represent truth and falsity. Equivalence predicates include eq?, which tests identity; eqv?, which tests value equivalence for numbers and characters; and equal?, which tests structural equivalence for compound data.

6.2 Numeric Operations

6.2.1 Exact Arithmetic

Exact arithmetic operations produce exact results when given exact arguments. Exact numbers are rational or integer. Operations include +, -, *, /, expt, sqrt, and rational operations like numerator and denominator.

6.2.2 Inexact Arithmetic

Inexact arithmetic uses floating-point representation. Inexact numbers are approximations. Operations like fl+, fl-, etc., are provided in the flonum library. Conversions between exact and inexact are done via exact->inexact and inexact->exact.

6.2.3 Numerical Type Predicates

Predicates such as number?, complex?, real?, rational?, integer? test the numeric type. Supplementary predicates like exact?, inexact?, zero?, positive?, negative?, odd?, even? are also standard.

6.3 List and Pair Operations

Standard list operations include cons, car, cdr, list, append, reverse, length, list-ref, map, for-each, filter, fold-left, fold-right (in the (rnrs lists) library). Pair accessors and mutators are provided: set-car!, set-cdr!.

6.4 Characters and Strings

Characters are represented using the #\ prefix (e.g., #\a, #\space). String operations include string, string-length, string-ref, string-set!, string-append, substring, string->list, list->string. Case conversion: char-upcase, char-downcase, string-upcase, string-downcase.

6.5 Vectors and Bytevectors

Vectors are homogeneous containers of arbitrary objects accessed via vector-ref and vector-set!. Bytevectors store octets; operations include bytevector, bytevector-length, bytevector-u8-ref, bytevector-u8-set!. Conversion: vector->list, list->vector, bytevector->u8-list, u8-list->bytevector.

6.6 Symbols and Hashtables

Symbols are interned strings. Operations: symbol->string, string->symbol. Hashtables are key-value stores with customizable hash functions and equality predicates. Standard procedures: make-hashtable, hashtable-set!, hashtable-ref, hashtable-delete!, hashtable-size.

6.7 Input and Output

6.7.1 Port Operations

Ports are abstractions for input and output sources/sinks. Operations: open-input-file, open-output-file, close-port, port?, input-port?, output-port?. Standard input, output, and error ports are accessible via current-input-port, current-output-port, current-error-port.

6.7.2 Binary and Textual I/O

Textual I/O uses characters and strings: read, write, display, peek-char, read-char, write-char. Binary I/O uses bytevectors: read-bytevector, write-bytevector, get-u8, put-u8. For mixed content, transcoding can convert between textual and binary representations.

6.8 Filesystem Operations

Filesystem operations are provided in the (rnrs files) library. They include file-exists?, delete-file, rename-file, create-symbolic-link, and directory operations such as current-directory, create-directory, delete-directory. File permissions and timestamps are also accessible.

7 Base Library

7.1 (rnrs base)

The (rnrs base) library includes the core bindings: primitive expression forms, standard procedures for booleans, numbers, pairs, lists, characters, strings, vectors, and symbols. It also includes syntax-rules and the basic I/O ports.

7.2 (rnrs arithmetic)

The (rnrs arithmetic) library provides additional numeric operations, including bitwise operations (bitwise-and, bitwise-ior, etc.), fixnum operations (for exact integers in a limited range), and flonum operations (for inexact reals).

7.3 (rnrs io)

The (rnrs io) library extends I/O with support for custom ports, buffered ports, and simple textual I/O. It includes procedures for reading and writing S-expressions, as well as binary I/O primitives.

7.4 (rnrs programs)

The (rnrs programs) library provides an interface for executing subprocesses and communicating with them via ports. Key procedures: process, process-run, process-wait, process-signal, process-kill.

7.5 (rnrs hashtables)

The (rnrs hashtables) library provides the standard hashtable type with mutable and immutable variants. It supports user-defined hash functions and equality predicates. Operations include make-eq-hashtable, make-eqv-hashtable, hashtable-copy, hashtable-keys, hashtable-entries.

8 Exceptions and Conditions

8.1 Condition Types

Conditions are objects that describe exceptional situations. R⁶RS defines several condition types: &serious, &error, &violation, &assertion, &implementation-restriction, &lexical, &syntax, &undefined, &io, &io-read, &io-write, &io-decoding, &io-encoding, &i/o, &i/o-port, &i/o-filename. Conditions can be compound, composed via condition and condition?.

8.2 Raising and Handling

8.2.1 guard and with-exception-handler

guard is a syntactic form that catches exceptions: (guard (var (cond-clause ...) ...) body). with-exception-handler installs a handler procedure for the dynamic extent of a thunk. Handlers are invoked when an exception is raised.

8.2.2 raise and raise-continuable

raise raises a non-continuable exception; the handler cannot return normally. raise-continuable raises a continuable exception; the handler can return a value that becomes the result of the raise expression.

8.3 Defining New Condition Types

New condition types can be defined using define-condition-type. The macro takes a type name, a supertype, a list of field names, and an optional predicate. Accessors for fields are automatically generated. Conditions can be created with make-condition or condition compound.

9 Records and Data Structures

9.1 Procedural Records

Procedural records are defined using make-record-type-descriptor and record-constructor-descriptor. They provide a way to create new data types with encapsulated fields.

9.1.1 Constructor, Accessor, Mutator

From a record type descriptor, users can construct a record constructor, field accessors, and mutators. The constructor takes arguments that correspond to the fields. Accessors are procedures like record-accessor, and mutators like record-mutator.

9.2 Explicit Naming and Sealed Records

Records can be explicitly named using record-type-descriptor with a symbolic name. Sealed records prohibit inheritance; the sealed? field of the record type descriptor controls this. Sealed records are more efficient and ensure that the type cannot be extended.

9.3 Generative and Nongenerative Records

Generative records create a new type each time the descriptor is created, while nongenerative records have a fixed identity based on their name. Nongenerative records are used for standard types like &condition and help with type equality across libraries.

10 Syntax-Case and Libraries

10.1 (rnrs syntax-case)

The (rnrs syntax-case) library provides the syntax-case macro system, which allows writing procedural macros with full access to the syntax object model. It includes syntax, quasisyntax, unsyntax, unsyntax-splicing, identifier?, bound-identifier=?, free-identifier=?, generate-temporaries, and datum->syntax.

10.2 Pattern Matching via syntax-rules

syntax-rules is exported from (rnrs base) and provides pattern-based macro definition without needing syntax-case. It uses pattern matching with ellipsis (...) to handle variable numbers of subforms. syntax-rules is hygienic and does not expose the underlying syntax object representation.

11 Standard I/O and File System Library

11.1 Port Positions and Reversibility

Ports have positions that can be queried and set using port-position and set-port-position!. Reversible ports can be read from and written to; they support seeking. The library (rnrs io ports) provides procedures for creating ports with custom position arithmetic.

11.2 File Transcoding

Transcoding converts between textual and binary representations. The library (rnrs io transcoding) provides transcoded-port which wraps a binary port with a codec (e.g., utf-8 codec). Correct transcoding ensures proper handling of Unicode characters.

11.3 Directory and File Utilities

The (rnrs files) library includes directory-list to enumerate files in a directory, file-attributes to query metadata (size, permissions, modification time), and create-temp-file to create temporary files. Symbolic links and hard links are supported on systems that provide them.

12 Unicode Support

12.1 Character Encoding

Characters in R⁶RS are Unicode scalar values, represented by #\ followed by the character or its name (e.g., #\λ, #\u03BB). The full Unicode repertoire is available. Strings are sequences of characters, and all string operations handle Unicode correctly.

12.2 String Normalization

String normalization ensures that equivalent Unicode strings have the same representation. The library (rnrs unicode) provides string-normalize-nfd, string-normalize-nfc, string-normalize-nfkd, and string-normalize-nfkc. Normalization is important for string comparison and searching.

12.3 Unicode Library (rnrs unicode)

The (rnrs unicode) library adds procedures for Unicode character properties: char-general-category, char-uppercase?, char-lowercase?, char-title-case?, char-digit?, etc. It also provides case folding with char-foldcase and string-foldcase, and case mapping with string-titlecase.

13 Compatibility and Migration

13.1 Differences from R⁵RS

Key differences between R⁶RS and R⁵RS include the introduction of libraries and the (rnrs ...) hierarchy, a new module system that replaces the single top-level namespace, and the deprecation of certain R⁵RS features such as transcript-on/off and dynamic-wind (replaced by parameter objects). R⁶RS also adds support for Unicode, exact rational arithmetic, and the condition system.

13.2 Deprecated Features

R⁶RS deprecates several features from R⁵RS: transcript-on and transcript-off are removed; load is not part of the standard but may be provided by implementations; eval is not defined in the base library but is available in (rnrs eval); interaction-environment and null-environment are not standard. The syntax-rules system in R⁶RS is more limited in pattern matching compared to R⁵RS's syntax-rules extension.

13.3 Writing Portable Code

To write portable R⁶RS code, programmers should use only standard libraries and avoid implementation-specific extensions. They should import explicit libraries rather than relying on the environment&#039;s top-level bindings. Code should avoid deprecated features and use the condition system for error handling. Using the (rnrs) compound library simplifies imports but may include bindings not needed; careful selection of specific libraries improves readability and portability.