Title

Feature import and feature-based conditional expansion

Author

Marc Feeley

Abstract

It is desirable that program which depend on additions to standard Scheme name those additions. SRFIs provide the specifications of these additions ("features"), and SRFI 0 provides the means to actually check and handle their correct use. It is anticipated that there will be two classes of features:

("Reader syntax" refers to aspects of the syntax described by the grammars in the Scheme reports.)

The former class of features will probably include most SRFIs, exemplified by the list library specified in SRFI 1. The latter class includes Unicode and different kinds of parentheses.

One of the goals of this SRFI is to have feature implementations map naturally to the module systems of the respective Scheme implementations, where such a module system exists.

The scope of bindings for the provided features will vary over different implementations. Thus, to achieve portability, a program should only use a feature after an appropriate import clause, even if some Scheme implementations provide it by default.

SRFI 0, unlike other "require"-like library systems common in the Lisp world, separates between testing for the sheer existence of a feature and actually using it. This allows programs to be written which function in a wide variety of different Scheme implementations.

Rationale

Most Scheme systems extend the language with some additional features (such as the ability to manipulate Unicode characters and strings, to do binary I/O, or to handle asynchronous interrupts). Such features may be provided in a variety of ways including new procedures, new program syntax, and extended behavior of standard procedures and special-forms. A particular functionality may exist in several or even most Scheme systems but its API may be different (use of a procedure or special-form, name, number of parameters, etc).

Constructs

Different Scheme systems provide language extensions by different means: Some have them built-in by default, some provide them via on-demand loading of code, some provide them via module systems. Combinations of these approaches also exist.

To write code that will run on several Scheme systems, it is useful to have common constructs to:

Features are identified by feature identifiers. In order for the semantics of this construct to be well-defined, the feature identifier must of course refer to a feature which has a well-defined meaning. There is thus a need for a registry, independent of this SRFI, to keep track of the formal specification associated with each valid feature-identifier. The SRFI registry is used for this purpose.

Another issue is the binding time of this construct (i.e. the moment when it operates). It is important that the binding time be early so that a compiler can discard the sections of code that are not needed, and perform better static analyses. Expressing this construct through a procedure returning a boolean, such as (feature-implemented? 'srfi-5), would not achieve this goal, as its binding time is too late (i.e. program run-time). A read-time construct, such as Common Lisp's #+ read-macro, is very early but would require non-trivial changes to the reader of existing Scheme systems and the syntax is not particularly human-friendly. Instead, a macro-expansion-time construct is used.

It is desirable to separate the act of checking for the existence of a feature from actually making it available for a Scheme program to use. This enables program to make more involved decisions about what features of the ones available it might use. Hence, the SRFI also specifies an IMPORT-IMPLEMENTATION form which makes the bindings provided by a feature available. Separating this from the conditional construct also makes for more readable programs, especially in cases where the program requires a fixed set of features, and the only decision it makes are is to determine if qthe features exist (and the program will run) or not (in which case it won't).

Macro-expansion time may not be early enough to handle features which are effectively changes to the reader used by the Scheme implementation. Therefore, a third construct, IMPORT-READER-SYNTAX, which may only occur at the beginning of a syntactic processing unit, specifies its syntax.

COND-IMPLEMENTS design

One valid way to implement COND-IMPLEMENTS is in terms of a simpler IF-IMPLEMENTS construct with dependencies on just one feature at a time, and just one or two branches. Why not specify IF-IMPLEMENTS instead?

The COND-IMPLEMENTS construct specified here gives Scheme implementations more flexibility in implementing it. The specification is intentionally ambiguous as to which clause will be expanded in a COND-IMPLEMENTS form. This is in order to allow Scheme implementations to choose an especially convenient (fastest/least memory-intensive/...) combination of implementations.

To demonstrate the utility of the conditional construct, consider the following example:

    (cond-implements
       (srfi-a ... aaa ...)
       (srfi-b ... bbb ...))

where the programmer is implementing some abstraction that can use function aaa from SRFI a or can use function bbb from SRFI b. However, the semantic fit with aaa is substantially better, which the programmer recognized by giving that implementation first. So if both SRFIs exist, and the "quality" of implementation is the comparable for SRFI a and SRFI b, then using the version with the better semantic fit is probably more efficient. However if they both exist, but SRFI a must be compiled and loaded from a slow reference implementation, and SRFI b is implemented as a built-in native-code library, it is preferable that the Scheme implementation should use the second version.

Specification

Syntax

<expression> --> (COND-IMPLEMENTS <cond-implements clause>+)
<command or definition>
             --> (IMPORT-IMPLEMENTATION <feature identifier>+)
<cond-implements clause>
             --> (<implementation requirement> <body>)
             --> (ELSE <body>)
<implementation requirement>
             --> <feature identifier>
             --> (AND <implementation requirement>*)
             --> (OR <implementation requirement>*)
             --> (NOT <implementation requirement>)
<feature identifier>
             --> srfi-<srfi number>
<srfi number> --> <nonzero digit 10> <digit 10>*
<nonzero digit 10> --> 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

<program>    --> <syntax import>? <command or definition>*
<syntax import> --> (IMPORT-READER-SYNTAX <feature identifier>+)

COND-IMPLEMENTS

COND-IMPLEMENTS tests for the existence of feature implementations. It either expands into the body of one of its clauses or signals an error during syntactic processing. COND-IMPLEMENTS expands into the body of a clause whose implementation requirement the underlying Scheme implementation can satisfy.

An implementation requirement has an obvious interpretation as a logical formula, where the <feature identifier> variables have meaning TRUE if an implementation corresponding to the named feature exists in the underlying Scheme implementation, and FALSE otherwise. An implementation requirement is satisfied if its formula is true under this interpretation.

It is unspecified into which body the COND-IMPLEMENTS form expands if several clauses have satisfiable requirements.

The existence of an implementation in no way implies that the bindings provided by it are actually visible. (But it is possible that bindings provided by implementations are visible by default.)

IMPORT-IMPLEMENTATION

IMPORT-IMPLEMENTATION makes available the value and syntactic bindings of implementations for the features specified by its arguments. If a feature specified in an IMPORT-IMPLEMENTATION clause does not exist, an error is signalled during syntactic processing. If the bindings relating to a feature implementation are already available, IMPORT-IMPLEMENTATION is a no-op. It is an error for the bindings imported by one or several IMPORT-IMPLEMENTATION forms to conflict with each other. The existence test here is the same as in COND-IMPLEMENTS.

It is an error for an IMPORT-IMPLEMENTATION to appear anywhere other than at toplevel.

IMPORT-READER-SYNTAX

In Scheme implementations which implement alternate reader syntaxes, IMPORT-READER-SYNTAX allows stating that a syntactic processing unit is written in the syntax specified by a SRFI. If a feature specified in an IMPORT-READER-SYNTAX clause does not have an implementation, an error is signalled during syntactic processing. The alternative syntax will be in effect immediately following the closing parenthesis of the IMPORT-READER-SYNTAX form. If the reader changes relating to a feature implementation are already in effect, IMPORT-READER-SYNTAX is a no-op. It is an error for the syntaxes imported by one or several IMPORT-READER-SYNTAX forms to conflict with each other. The existence test here is the same as in COND-IMPLEMENTS.

It is an error for an IMPORT-READER-SYNTAX to appear anywhere but as the first form in a syntactic processing unit or within that first form, either literally or as a result of macro expansion.

Appendix: Implementation Issues

A feature implementation may be, for example:

The latter two possibilities allow loading or linking a feature implementation "on-demand" only for programs which actually use them. Some Scheme implementations might use the underlying package system to implement on-demand loading. Batch implementations might process a program using SRFI 0 to generate link commands. Other possibilities exist.

It is the intention that, if the implementation has a choice between equivalent "quality," it will choose the first matching clause in left-to-right order.

In Scheme implementations where feature implementations are loadable, COND-IMPLEMENTS by itself should merely test for the existence of implementations, but not actually load any code, import any bindings, or make any reader changes. The actual loading/importing only happens through IMPORT-IMPLEMENTATION and IMPORT-READER-SYNTAX.

In Scheme implementations where feature implementations are not loadable, COND-IMPLEMENTS is a simple feature test, and IMPORT-IMPLEMENTATION and IMPORT-READER-SYNTAX are no-ops if they do not signal an error.

The issue of alternative reader syntax is the most difficult. The specification of IMPORT-READER-SYNTAX makes the assumption that one syntactic processing unit has a single syntax throughout. It is convenient to have the processing unit specify which syntax it is written in, rather than specifying the syntax externally.

Since changing the reader syntax may have complex interactions with macro expansion and other syntactic processing, the IMPORT-READER-SYNTAX must appear at the beginning of a processing unit. The intention is that a Scheme implementation can determine the syntax of a processing unit by retrieving the first form in a unit via READ and macro-expanding that form. This makes the proposal compatible with Scheme implementations which depend on reading in a whole processing unit before macro expansion.

A consequence of the specification is that an initial form specifying a syntax change can really only use LET-SYNTAX, LETREC-SYNTAX, uses of macros defined by them, and COND-IMPLEMENTS.

Scheme implementations that do not support changable readers need not handle a COND-IMPLEMENTS that appears as the first form specially. Here, IMPORT-READER-SYNTAX may be a simple macro.

Implementation

The simplest implementation of SRFI 0 assumes that all existent features are built-in.

Nota bene: This implementation only demonstrates implementability under R5RS Scheme. It is by no means a reference implementation to be used in actual Scheme systems. Moreover, the behavior of this implementation is a significant restriction on the specification.

(define-syntax import-implementation
  (syntax-rules ()
    ((import-implementation feature-id ...)
     (cond-implements (and feature-id ...) 42))))

(define-syntax import-reader-syntax
  (syntax-rules ()
    ((import-reader-syntax feature-id ...)
     (cond-implements (and feature-id ...) 42))))

(The specification requires a macro-expansion time error to be signalled if a COND-IMPLEMENTS has no fulfilled clause. There is no standard way to so that, so this implementation uses the SYNTAX-ERROR form, with the assumption that an actual implementation would provide some way to do this.)

(define-syntax cond-implements
  (syntax-rules (and or not else srfi-0 srfi-5)
    ((cond-implements) (syntax-error "Unfulfilled COND-IMPLEMENTS"))
    ((cond-implements (else body ...))
     (begin body ...))
    ((cond-implements ((and) body ...) more-clauses ...)
     (begin body ...))
    ((cond-implements ((and req1 req2 ...) body ...) more-clauses ...)
     (cond-implements
       (req1
         (cond-implements
           ((and req2 ...) body ...)
           more-clauses ...))
       more-clauses ...))
    ((cond-implements ((or) body ...) more-clauses ...)
     (cond-implements more-clauses ...))
    ((cond-implements ((or req1 req2 ...) body ...) more-clauses ...)
     (cond-implements
       (req1
        (begin body ...))
       (else
        (cond-implements
           ((or req2 ...) body ...)
           more-clauses ...))))
    ((cond-implements ((not req) body ...) more-clauses ...)
     (cond-implements
       (req
         (cond-implements more-clauses ...))
       (else body ...)))
    ((cond-implements (srfi-0 body ...) more-clauses ...)
       (begin body...))
    ((cond-implements (srfi-5 body ...) more-clauses ...)
       (begin body...))
    ((cond-implements (feature-id body ...) more-clauses ...)
       (cond-implements more-clauses ...))))

Copyright

Copyright (C) Marc Feeley (1999). All Rights Reserved.

This document and translations of it may be copied and furnished to others, and derivative works that comment on or otherwise explain it or assist in its implementation may be prepared, copied, published and distributed, in whole or in part, without restriction of any kind, provided that the above copyright notice and this paragraph are included on all such copies and derivative works. However, this document itself may not be modified in any way, such as by removing the copyright notice or references to the Scheme Request For Implementation process or editors, except as needed for the purpose of developing SRFIs in which case the procedures for copyrights defined in the SRFI process must be followed, or as required to translate it into languages other than English.

The limited permissions granted above are perpetual and will not be revoked by the authors or their successors or assigns.

This document and the information contained herein is provided on an "AS IS" basis and THE AUTHOR AND THE SRFI EDITORS DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.


Editor: Mike Sperber
Last modified: Fri Feb 19 11:00:17 MET 1999