[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3. Scheme Syntax Extension Packages

3.1 Defmacro  Supported by all implementations
3.2 R4RS Macros  'macro
3.3 Macro by Example  'macro-by-example
3.4 Macros That Work  'macros-that-work
3.5 Syntactic Closures  'syntactic-closures
3.6 Syntax-Case Macros  'syntax-case
Syntax extensions (macros) included with SLIB.
3.7 Fluid-Let  'fluid-let
3.8 Yasos  'yasos, 'oop, 'collect


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.1 Defmacro

Defmacros are supported by all implementations.

Function: gentemp
Returns a new (interned) symbol each time it is called. The symbol names are implementation-dependent
 
(gentemp) => scm:G0
(gentemp) => scm:G1

Function: defmacro:eval e
Returns the slib:eval of expanding all defmacros in scheme expression e.

Function: defmacro:load filename
filename should be a string. If filename names an existing file, the defmacro:load procedure reads Scheme source code expressions and definitions from the file and evaluates them sequentially. These source code expressions and definitions may contain defmacro definitions. The macro:load procedure does not affect the values returned by current-input-port and current-output-port.

Function: defmacro? sym
Returns #t if sym has been defined by defmacro, #f otherwise.

Function: macroexpand-1 form
Function: macroexpand form
If form is a macro call, macroexpand-1 will expand the macro call once and return it. A form is considered to be a macro call only if it is a cons whose car is a symbol for which a defmacro has been defined.

macroexpand is similar to macroexpand-1, but repeatedly expands form until it is no longer a macro call.

Macro: defmacro name lambda-list form ...
When encountered by defmacro:eval, defmacro:macroexpand*, or defmacro:load defines a new macro which will henceforth be expanded when encountered by defmacro:eval, defmacro:macroexpand*, or defmacro:load.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.1.1 Defmacroexpand

(require 'defmacroexpand)

Function: defmacro:expand* e
Returns the result of expanding all defmacros in scheme expression e.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.2 R4RS Macros

(require 'macro) is the appropriate call if you want R4RS high-level macros but don't care about the low level implementation. If an SLIB R4RS macro implementation is already loaded it will be used. Otherwise, one of the R4RS macros implemetations is loaded.

The SLIB R4RS macro implementations support the following uniform interface:

Function: macro:expand sexpression
Takes an R4RS expression, macro-expands it, and returns the result of the macro expansion.

Function: macro:eval sexpression
Takes an R4RS expression, macro-expands it, evals the result of the macro expansion, and returns the result of the evaluation.

Procedure: macro:load filename
filename should be a string. If filename names an existing file, the macro:load procedure reads Scheme source code expressions and definitions from the file and evaluates them sequentially. These source code expressions and definitions may contain macro definitions. The macro:load procedure does not affect the values returned by current-input-port and current-output-port.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.3 Macro by Example

(require 'macro-by-example)

A vanilla implementation of Macro by Example (Eugene Kohlbecker, R4RS) by Dorai Sitaram, (dorai @ cs.rice.edu) using defmacro.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.3.1 Caveat

These macros are not referentially transparent (see section `Macros' in Revised(4) Scheme). Lexically scoped macros (i.e., let-syntax and letrec-syntax) are not supported. In any case, the problem of referential transparency gains poignancy only when let-syntax and letrec-syntax are used. So you will not be courting large-scale disaster unless you're using system-function names as local variables with unintuitive bindings that the macro can't use. However, if you must have the full r4rs macro functionality, look to the more featureful (but also more expensive) versions of syntax-rules available in slib 3.4 Macros That Work, 3.5 Syntactic Closures, and 3.6 Syntax-Case Macros.

Macro: define-syntax keyword transformer-spec
The keyword is an identifier, and the transformer-spec should be an instance of syntax-rules.

The top-level syntactic environment is extended by binding the keyword to the specified transformer.

 
(define-syntax let*
  (syntax-rules ()
    ((let* () body1 body2 ...)
     (let () body1 body2 ...))
    ((let* ((name1 val1) (name2 val2) ...)
       body1 body2 ...)
     (let ((name1 val1))
       (let* (( name2 val2) ...)
         body1 body2 ...)))))

Macro: syntax-rules literals syntax-rule ...
literals is a list of identifiers, and each syntax-rule should be of the form

(pattern template)

where the pattern and template are as in the grammar above.

An instance of syntax-rules produces a new macro transformer by specifying a sequence of hygienic rewrite rules. A use of a macro whose keyword is associated with a transformer specified by syntax-rules is matched against the patterns contained in the syntax-rules, beginning with the leftmost syntax-rule. When a match is found, the macro use is trancribed hygienically according to the template.

Each pattern begins with the keyword for the macro. This keyword is not involved in the matching and is not considered a pattern variable or literal identifier.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.4 Macros That Work

(require 'macros-that-work)

Macros That Work differs from the other R4RS macro implementations in that it does not expand derived expression types to primitive expression types.

Function: macro:expand expression
Function: macwork:expand expression
Takes an R4RS expression, macro-expands it, and returns the result of the macro expansion.

Function: macro:eval expression
Function: macwork:eval expression
macro:eval returns the value of expression in the current top level environment. expression can contain macro definitions. Side effects of expression will affect the top level environment.

Procedure: macro:load filename
Procedure: macwork:load filename
filename should be a string. If filename names an existing file, the macro:load procedure reads Scheme source code expressions and definitions from the file and evaluates them sequentially. These source code expressions and definitions may contain macro definitions. The macro:load procedure does not affect the values returned by current-input-port and current-output-port.

References:

The Revised^4 Report on the Algorithmic Language Scheme Clinger and Rees [editors]. To appear in LISP Pointers. Also available as a technical report from the University of Oregon, MIT AI Lab, and Cornell.

Macros That Work. Clinger and Rees. POPL '91.

The supported syntax differs from the R4RS in that vectors are allowed as patterns and as templates and are not allowed as pattern or template data.

 
transformer spec  ==>  (syntax-rules literals rules)

rules  ==>  ()
         |  (rule . rules)

rule  ==>  (pattern template)

pattern  ==>  pattern_var      ; a symbol not in literals
           |  symbol           ; a symbol in literals
           |  ()
           |  (pattern . pattern)
           |  (ellipsis_pattern)
           |  #(pattern*)                     ; extends R4RS
           |  #(pattern* ellipsis_pattern)    ; extends R4RS
           |  pattern_datum

template  ==>  pattern_var
            |  symbol
            |  ()
            |  (template2 . template2)
            |  #(template*)                   ; extends R4RS
            |  pattern_datum

template2  ==>  template
             |  ellipsis_template

pattern_datum  ==>  string                    ; no vector
                 |  character
                 |  boolean
                 |  number

ellipsis_pattern  ==> pattern ...

ellipsis_template  ==>  template ...

pattern_var  ==>  symbol   ; not in literals

literals  ==>  ()
            |  (symbol . literals)


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.4.1 Definitions

Scope of an ellipsis
Within a pattern or template, the scope of an ellipsis (...) is the pattern or template that appears to its left.

Rank of a pattern variable
The rank of a pattern variable is the number of ellipses within whose scope it appears in the pattern.

Rank of a subtemplate
The rank of a subtemplate is the number of ellipses within whose scope it appears in the template.

Template rank of an occurrence of a pattern variable
The template rank of an occurrence of a pattern variable within a template is the rank of that occurrence, viewed as a subtemplate.

Variables bound by a pattern
The variables bound by a pattern are the pattern variables that appear within it.

Referenced variables of a subtemplate
The referenced variables of a subtemplate are the pattern variables that appear within it.

Variables opened by an ellipsis template
The variables opened by an ellipsis template are the referenced pattern variables whose rank is greater than the rank of the ellipsis template.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.4.2 Restrictions

No pattern variable appears more than once within a pattern.

For every occurrence of a pattern variable within a template, the template rank of the occurrence must be greater than or equal to the pattern variable's rank.

Every ellipsis template must open at least one variable.

For every ellipsis template, the variables opened by an ellipsis template must all be bound to sequences of the same length.

The compiled form of a rule is

 
rule  ==>  (pattern template inserted)

pattern  ==>  pattern_var
           |  symbol
           |  ()
           |  (pattern . pattern)
           |  ellipsis_pattern
           |  #(pattern)
           |  pattern_datum

template  ==>  pattern_var
            |  symbol
            |  ()
            |  (template2 . template2)
            |  #(pattern)
            |  pattern_datum

template2  ==>  template
             |  ellipsis_template

pattern_datum  ==>  string
                 |  character
                 |  boolean
                 |  number

pattern_var  ==>  #(V symbol rank)

ellipsis_pattern  ==>  #(E pattern pattern_vars)

ellipsis_template  ==>  #(E template pattern_vars)

inserted  ==>  ()
            |  (symbol . inserted)

pattern_vars  ==>  ()
                |  (pattern_var . pattern_vars)

rank  ==>  exact non-negative integer

where V and E are unforgeable values.

The pattern variables associated with an ellipsis pattern are the variables bound by the pattern, and the pattern variables associated with an ellipsis template are the variables opened by the ellipsis template.

If the template contains a big chunk that contains no pattern variables or inserted identifiers, then the big chunk will be copied unnecessarily. That shouldn't matter very often.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.5 Syntactic Closures

(require 'syntactic-closures)

Function: macro:expand expression
Function: synclo:expand expression
Returns scheme code with the macros and derived expression types of expression expanded to primitive expression types.

Function: macro:eval expression
Function: synclo:eval expression
macro:eval returns the value of expression in the current top level environment. expression can contain macro definitions. Side effects of expression will affect the top level environment.

Procedure: macro:load filename
Procedure: synclo:load filename
filename should be a string. If filename names an existing file, the macro:load procedure reads Scheme source code expressions and definitions from the file and evaluates them sequentially. These source code expressions and definitions may contain macro definitions. The macro:load procedure does not affect the values returned by current-input-port and current-output-port.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.5.1 Syntactic Closure Macro Facility

A Syntactic Closures Macro Facility
by Chris Hanson
9 November 1991

This document describes syntactic closures, a low-level macro facility for the Scheme programming language. The facility is an alternative to the low-level macro facility described in the Revised^4 Report on Scheme. This document is an addendum to that report.

The syntactic closures facility extends the BNF rule for transformer spec to allow a new keyword that introduces a low-level macro transformer:

 
transformer spec := (transformer expression)

Additionally, the following procedures are added:

 
make-syntactic-closure
capture-syntactic-environment
identifier?
identifier=?

The description of the facility is divided into three parts. The first part defines basic terminology. The second part describes how macro transformers are defined. The third part describes the use of identifiers, which extend the syntactic closure mechanism to be compatible with syntax-rules.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.5.1.1 Terminology

This section defines the concepts and data types used by the syntactic closures facility.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.5.1.2 Transformer Definition

This section describes the transformer special form and the procedures make-syntactic-closure and capture-syntactic-environment.

Syntax: transformer expression

Syntax: It is an error if this syntax occurs except as a transformer spec.

Semantics: The expression is evaluated in the standard transformer environment to yield a macro transformer as described below. This macro transformer is bound to a macro keyword by the special form in which the transformer expression appears (for example, let-syntax).

A macro transformer is a procedure that takes two arguments, a form and a syntactic environment, and returns a new form. The first argument, the input form, is the form in which the macro keyword occurred. The second argument, the usage environment, is the syntactic environment in which the input form occurred. The result of the transformer, the output form, is automatically closed in the transformer environment, which is the syntactic environment in which the transformer expression occurred.

For example, here is a definition of a push macro using syntax-rules:

 
(define-syntax  push
  (syntax-rules ()
    ((push item list)
     (set! list (cons item list)))))

Here is an equivalent definition using transformer:

 
(define-syntax push
  (transformer
   (lambda (exp env)
     (let ((item
            (make-syntactic-closure env '() (cadr exp)))
           (list
            (make-syntactic-closure env '() (caddr exp))))
       `(set! ,list (cons ,item ,list))))))

In this example, the identifiers set! and cons are closed in the transformer environment, and thus will not be affected by the meanings of those identifiers in the usage environment env.

Some macros may be non-hygienic by design. For example, the following defines a loop macro that implicitly binds exit to an escape procedure. The binding of exit is intended to capture free references to exit in the body of the loop, so exit must be left free when the body is closed:

 
(define-syntax loop
  (transformer
   (lambda (exp env)
     (let ((body (cdr exp)))
       `(call-with-current-continuation
         (lambda (exit)
           (let f ()
             ,@(map (lambda  (exp)
                       (make-syntactic-closure env '(exit)
                                               exp))
                     body)
             (f))))))))

To assign meanings to the identifiers in a form, use make-syntactic-closure to close the form in a syntactic environment.

Function: make-syntactic-closure environment free-names form

environment must be a syntactic environment, free-names must be a list of identifiers, and form must be a form. make-syntactic-closure constructs and returns a syntactic closure of form in environment, which can be used anywhere that form could have been used. All the identifiers used in form, except those explicitly excepted by free-names, obtain their meanings from environment.

Here is an example where free-names is something other than the empty list. It is instructive to compare the use of free-names in this example with its use in the loop example above: the examples are similar except for the source of the identifier being left free.

 
(define-syntax let1
  (transformer
   (lambda (exp env)
     (let ((id (cadr exp))
           (init (caddr exp))
           (exp (cadddr exp)))
       `((lambda (,id)
           ,(make-syntactic-closure env (list id) exp))
         ,(make-syntactic-closure env '() init))))))

let1 is a simplified version of let that only binds a single identifier, and whose body consists of a single expression. When the body expression is syntactically closed in its original syntactic environment, the identifier that is to be bound by let1 must be left free, so that it can be properly captured by the lambda in the output form.

To obtain a syntactic environment other than the usage environment, use capture-syntactic-environment.

Function: capture-syntactic-environment procedure

capture-syntactic-environment returns a form that will, when transformed, call procedure on the current syntactic environment. procedure should compute and return a new form to be transformed, in that same syntactic environment, in place of the form.

An example will make this clear. Suppose we wanted to define a simple loop-until keyword equivalent to

 
(define-syntax loop-until
  (syntax-rules ()
    ((loop-until id init test return step)
     (letrec ((loop
               (lambda (id)
                 (if test return (loop step)))))
       (loop init)))))

The following attempt at defining loop-until has a subtle bug:

 
(define-syntax loop-until
  (transformer
   (lambda (exp env)
     (let ((id (cadr exp))
           (init (caddr exp))
           (test (cadddr exp))
           (return (cadddr (cdr exp)))
           (step (cadddr (cddr exp)))
           (close
            (lambda (exp free)
              (make-syntactic-closure env free exp))))
       `(letrec ((loop
                  (lambda (,id)
                    (if ,(close test (list id))
                        ,(close return (list id))
                        (loop ,(close step (list id)))))))
          (loop ,(close init '())))))))

This definition appears to take all of the proper precautions to prevent unintended captures. It carefully closes the subexpressions in their original syntactic environment and it leaves the id identifier free in the test, return, and step expressions, so that it will be captured by the binding introduced by the lambda expression. Unfortunately it uses the identifiers if and loop within that lambda expression, so if the user of loop-until just happens to use, say, if for the identifier, it will be inadvertently captured.

The syntactic environment that if and loop want to be exposed to is the one just outside the lambda expression: before the user's identifier is added to the syntactic environment, but after the identifier loop has been added. capture-syntactic-environment captures exactly that environment as follows:

 
(define-syntax loop-until
  (transformer
   (lambda (exp env)
     (let ((id (cadr exp))
           (init (caddr exp))
           (test (cadddr exp))
           (return (cadddr (cdr exp)))
           (step (cadddr (cddr exp)))
           (close
            (lambda (exp free)
              (make-syntactic-closure env free exp))))
       `(letrec ((loop
                  ,(capture-syntactic-environment
                    (lambda (env)
                      `(lambda (,id)
                         (,(make-syntactic-closure env '() `if)
                          ,(close test (list id))
                          ,(close return (list id))
                          (,(make-syntactic-closure env '()
                                                    `loop)
                           ,(close step (list id)))))))))
          (loop ,(close init '())))))))

In this case, having captured the desired syntactic environment, it is convenient to construct syntactic closures of the identifiers if and the loop and use them in the body of the lambda.

A common use of capture-syntactic-environment is to get the transformer environment of a macro transformer:

 
(transformer
 (lambda (exp env)
   (capture-syntactic-environment
    (lambda (transformer-env)
      ...))))


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.5.1.3 Identifiers

This section describes the procedures that create and manipulate identifiers. Previous syntactic closure proposals did not have an identifier data type -- they just used symbols. The identifier data type extends the syntactic closures facility to be compatible with the high-level syntax-rules facility.

As discussed earlier, an identifier is either a symbol or an alias. An alias is implemented as a syntactic closure whose form is an identifier:

 
(make-syntactic-closure env '() 'a)
   => an alias

Aliases are implemented as syntactic closures because they behave just like syntactic closures most of the time. The difference is that an alias may be bound to a new value (for example by lambda or let-syntax); other syntactic closures may not be used this way. If an alias is bound, then within the scope of that binding it is looked up in the syntactic environment just like any other identifier.

Aliases are used in the implementation of the high-level facility syntax-rules. A macro transformer created by syntax-rules uses a template to generate its output form, substituting subforms of the input form into the template. In a syntactic closures implementation, all of the symbols in the template are replaced by aliases closed in the transformer environment, while the output form itself is closed in the usage environment. This guarantees that the macro transformation is hygienic, without requiring the transformer to know the syntactic roles of the substituted input subforms.

Function: identifier? object
Returns #t if object is an identifier, otherwise returns #f. Examples:

 
(identifier? 'a)
   => #t
(identifier? (make-syntactic-closure env '() 'a))
   => #t
(identifier? "a")
   => #f
(identifier? #\a)
   => #f
(identifier? 97)
   => #f
(identifier? #f)
   => #f
(identifier? '(a))
   => #f
(identifier? '#(a))
   => #f

The predicate eq? is used to determine if two identifers are "the same". Thus eq? can be used to compare identifiers exactly as it would be used to compare symbols. Often, though, it is useful to know whether two identifiers "mean the same thing". For example, the cond macro uses the symbol else to identify the final clause in the conditional. A macro transformer for cond cannot just look for the symbol else, because the cond form might be the output of another macro transformer that replaced the symbol else with an alias. Instead the transformer must look for an identifier that "means the same thing" in the usage environment as the symbol else means in the transformer environment.

Function: identifier=? environment1 identifier1 environment2 identifier2
environment1 and environment2 must be syntactic environments, and identifier1 and identifier2 must be identifiers. identifier=? returns #t if the meaning of identifier1 in environment1 is the same as that of identifier2 in environment2, otherwise it returns #f. Examples:

 
(let-syntax
    ((foo
      (transformer
       (lambda (form env)
         (capture-syntactic-environment
          (lambda (transformer-env)
            (identifier=? transformer-env 'x env 'x)))))))
  (list (foo)
        (let ((x 3))
          (foo))))
   => (#t #f)

 
(let-syntax ((bar foo))
  (let-syntax
      ((foo
        (transformer
         (lambda (form env)
           (capture-syntactic-environment
            (lambda (transformer-env)
              (identifier=? transformer-env 'foo
                            env (cadr form))))))))
    (list (foo foo)
          (foobar))))
   => (#f #t)


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.5.1.4 Acknowledgements

The syntactic closures facility was invented by Alan Bawden and Jonathan Rees. The use of aliases to implement syntax-rules was invented by Alan Bawden (who prefers to call them synthetic names). Much of this proposal is derived from an earlier proposal by Alan Bawden.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.6 Syntax-Case Macros

(require 'syntax-case)

Function: macro:expand expression
Function: syncase:expand expression
Returns scheme code with the macros and derived expression types of expression expanded to primitive expression types.

Function: macro:eval expression
Function: syncase:eval expression
macro:eval returns the value of expression in the current top level environment. expression can contain macro definitions. Side effects of expression will affect the top level environment.

Procedure: macro:load filename
Procedure: syncase:load filename
filename should be a string. If filename names an existing file, the macro:load procedure reads Scheme source code expressions and definitions from the file and evaluates them sequentially. These source code expressions and definitions may contain macro definitions. The macro:load procedure does not affect the values returned by current-input-port and current-output-port.

This is version 2.1 of syntax-case, the low-level macro facility proposed and implemented by Robert Hieb and R. Kent Dybvig.

This version is further adapted by Harald Hanche-Olsen <hanche @ imf.unit.no> to make it compatible with, and easily usable with, SLIB. Mainly, these adaptations consisted of:

If you wish, you can see exactly what changes were done by reading the shell script in the file `syncase.sh'.

The two PostScript files were omitted in order to not burden the SLIB distribution with them. If you do intend to use syntax-case, however, you should get these files and print them out on a PostScript printer. They are available with the original syntax-case distribution by anonymous FTP in `cs.indiana.edu:/pub/scheme/syntax-case'.

In order to use syntax-case from an interactive top level, execute:

 
(require 'syntax-case)
(require 'repl)
(repl:top-level macro:eval)
See the section Repl (see section 7.5.1 Repl) for more information.

To check operation of syntax-case get `cs.indiana.edu:/pub/scheme/syntax-case', and type

 
(require 'syntax-case)
(syncase:sanity-check)

Beware that syntax-case takes a long time to load -- about 20s on a SPARCstation SLC (with SCM) and about 90s on a Macintosh SE/30 (with Gambit).


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.6.1 Notes

All R4RS syntactic forms are defined, including delay. Along with delay are simple definitions for make-promise (into which delay expressions expand) and force.

syntax-rules and with-syntax (described in TR356) are defined.

syntax-case is actually defined as a macro that expands into calls to the procedure syntax-dispatch and the core form syntax-lambda; do not redefine these names.

Several other top-level bindings not documented in TR356 are created:

The syntax of define has been extended to allow (define id), which assigns id to some unspecified value.

We have attempted to maintain R4RS compatibility where possible. The incompatibilities should be confined to `hooks.ss'. Please let us know if there is some incompatibility that is not flagged as such.

Send bug reports, comments, suggestions, and questions to Kent Dybvig (dyb @ iuvax.cs.indiana.edu).


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.6.2 Note from SLIB maintainer

(require 'structure)

Included with the syntax-case files was `structure.scm' which defines a macro define-structure. I have no documentation for this macro; it is not used by any other code in SLIB.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.7 Fluid-Let

(require 'fluid-let)

Syntax: fluid-let (bindings ...) forms...
 
(fluid-let ((variable init) ...)
   expression expression ...)

The inits are evaluated in the current environment (in some unspecified order), the current values of the variables are saved, the results are assigned to the variables, the expressions are evaluated sequentially in the current environment, the variables are restored to their original values, and the value of the last expression is returned.

The syntax of this special form is similar to that of let, but fluid-let temporarily rebinds existing variables. Unlike let, fluid-let creates no new bindings; instead it assigns the values of each init to the binding (determined by the rules of lexical scoping) of its corresponding variable.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.8 Yasos

(require 'oop) or (require 'yasos)

`Yet Another Scheme Object System' is a simple object system for Scheme based on the paper by Norman Adams and Jonathan Rees: Object Oriented Programming in Scheme, Proceedings of the 1988 ACM Conference on LISP and Functional Programming, July 1988 [ACM #552880].

Another reference is:

Ken Dickey. <A HREF="ftp://ftp.cs.indiana.edu/pub/scheme-repository/doc/pubs/swob.txt"> Scheming with Objects </A> AI Expert Volume 7, Number 10 (October 1992), pp. 24-33.

3.8.1 Terms  Definitions and disclaimer.
3.8.2 Interface  The Yasos macros and procedures.
3.8.3 Setters  Dylan-like setters in Yasos.
3.8.4 Examples  Usage of Yasos and setters.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.8.1 Terms

Object
Any Scheme data object.

Instance
An instance of the OO system; an object.

Operation
A method.

Notes:
The object system supports multiple inheritance. An instance can inherit from 0 or more ancestors. In the case of multiple inherited operations with the same identity, the operation used is that from the first ancestor which contains it (in the ancestor let). An operation may be applied to any Scheme data object--not just instances. As code which creates instances is just code, there are no classes and no meta-anything. Method dispatch is by a procedure call a la CLOS rather than by send syntax a la Smalltalk.

Disclaimer:
There are a number of optimizations which can be made. This implementation is expository (although performance should be quite reasonable). See the L&FP paper for some suggestions.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.8.2 Interface

Syntax: define-operation (opname self arg ...) default-body
Defines a default behavior for data objects which don't handle the operation opname. The default behavior (for an empty default-body) is to generate an error.

Syntax: define-predicate opname?
Defines a predicate opname?, usually used for determining the type of an object, such that (opname? object) returns #t if object has an operation opname? and #f otherwise.

Syntax: object ((name self arg ...) body) ...
Returns an object (an instance of the object system) with operations. Invoking (name object arg ... executes the body of the object with self bound to object and with argument(s) arg....

Syntax: object-with-ancestors ((ancestor1 init1) ...) operation ...
A let-like form of object for multiple inheritance. It returns an object inheriting the behaviour of ancestor1 etc. An operation will be invoked in an ancestor if the object itself does not provide such a method. In the case of multiple inherited operations with the same identity, the operation used is the one found in the first ancestor in the ancestor list.

Syntax: operate-as component operation self arg ...
Used in an operation definition (of self) to invoke the operation in an ancestor component but maintain the object's identity. Also known as "send-to-super".

Procedure: print obj port
A default print operation is provided which is just (format port obj) (see section 4.2 Format (version 3.0)) for non-instances and prints obj preceded by `#<INSTANCE>' for instances.

Function: size obj
The default method returns the number of elements in obj if it is a vector, string or list, 2 for a pair, 1 for a character and by default id an error otherwise. Objects such as collections (see section 7.1.9 Collections) may override the default in an obvious way.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.8.3 Setters

Setters implement generalized locations for objects associated with some sort of mutable state. A getter operation retrieves a value from a generalized location and the corresponding setter operation stores a value into the location. Only the getter is named -- the setter is specified by a procedure call as below. (Dylan uses special syntax.) Typically, but not necessarily, getters are access operations to extract values from Yasos objects (see section 3.8 Yasos). Several setters are predefined, corresponding to getters car, cdr, string-ref and vector-ref e.g., (setter car) is equivalent to set-car!.

This implementation of setters is similar to that in Dylan(TM) (Dylan: An object-oriented dynamic language, Apple Computer Eastern Research and Technology). Common LISP provides similar facilities through setf.

Function: setter getter
Returns the setter for the procedure getter. E.g., since string-ref is the getter corresponding to a setter which is actually string-set!:
 
(define foo "foo")
((setter string-ref) foo 0 #\F) ; set element 0 of foo
foo => "Foo"

Syntax: set place new-value
If place is a variable name, set is equivalent to set!. Otherwise, place must have the form of a procedure call, where the procedure name refers to a getter and the call indicates an accessible generalized location, i.e., the call would return a value. The return value of set is usually unspecified unless used with a setter whose definition guarantees to return a useful value.
 
(set (string-ref foo 2) #\O)  ; generalized location with getter
foo => "FoO"
(set foo "foo")               ; like set!
foo => "foo"

Procedure: add-setter getter setter
Add procedures getter and setter to the (inaccessible) list of valid setter/getter pairs. setter implements the store operation corresponding to the getter access operation for the relevant state. The return value is unspecified.

Procedure: remove-setter-for getter
Removes the setter corresponding to the specified getter from the list of valid setters. The return value is unspecified.

Syntax: define-access-operation getter-name
Shorthand for a Yasos define-operation defining an operation getter-name that objects may support to return the value of some mutable state. The default operation is to signal an error. The return value is unspecified.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.8.4 Examples

 
;;; These definitions for PRINT and SIZE are
;;; already supplied by
(require 'yasos)

(define-operation (print obj port)
  (format port
          (if (instance? obj) "#<instance>" "~s")
          obj))

(define-operation (size obj)
  (cond
   ((vector? obj) (vector-length obj))
   ((list?   obj) (length obj))
   ((pair?   obj) 2)
   ((string? obj) (string-length obj))
   ((char?   obj) 1)
   (else
    (slib:error "Operation not supported: size" obj))))

(define-predicate cell?)
(define-operation (fetch obj))
(define-operation (store! obj newValue))

(define (make-cell value)
  (object
   ((cell? self) #t)
   ((fetch self) value)
   ((store! self newValue)
    (set! value newValue)
    newValue)
   ((size self) 1)
   ((print self port)
    (format port "#<Cell: ~s>" (fetch self)))))

(define-operation (discard obj value)
  (format #t "Discarding ~s~%" value))

(define (make-filtered-cell value filter)
  (object-with-ancestors
   ((cell (make-cell value)))
   ((store! self newValue)
   (if (filter newValue)
       (store! cell newValue)
       (discard self newValue)))))

(define-predicate array?)
(define-operation (array-ref array index))
(define-operation (array-set! array index value))

(define (make-array num-slots)
  (let ((anArray (make-vector num-slots)))
    (object
     ((array? self) #t)
     ((size self) num-slots)
     ((array-ref self index)
      (vector-ref  anArray index))
     ((array-set! self index newValue)
      (vector-set! anArray index newValue))
     ((print self port)
      (format port "#<Array ~s>" (size self))))))

(define-operation (position obj))
(define-operation (discarded-value obj))

(define (make-cell-with-history value filter size)
  (let ((pos 0) (most-recent-discard #f))
    (object-with-ancestors
     ((cell (make-filtered-call value filter))
      (sequence (make-array size)))
     ((array? self) #f)
     ((position self) pos)
     ((store! self newValue)
      (operate-as cell store! self newValue)
      (array-set! self pos newValue)
      (set! pos (+ pos 1)))
     ((discard self value)
      (set! most-recent-discard value))
     ((discarded-value self) most-recent-discard)
     ((print self port)
      (format port "#<Cell-with-history ~s>"
              (fetch self))))))

(define-access-operation fetch)
(add-setter fetch store!)
(define foo (make-cell 1))
(print foo #f)
=> "#<Cell: 1>"
(set (fetch foo) 2)
=>
(print foo #f)
=> "#<Cell: 2>"
(fetch foo)
=> 2


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Steve Langasek on January, 10 2005 using texi2html