Go to the previous, next section.
Defmacros are supported by all implementations.
Returns a new (interned) symbol each time it is called. The symbol names are implementation-dependent
(gentemp) => scm:G0 (gentemp) => scm:G1
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
.
Returns #t
if sym has been defined by defmacro
,
#f
otherwise.
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
defmacr
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
.
(require 'defmacroexpand)
Returns the result of expanding all defmacros in scheme expression e.
(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
.
(require 'macro-by-example)
A vanilla implementation of Macro by Example (Eugene Kohlbecker,
R4RS) by Dorai Sitaram, (dorai@cs.rice.edu) using defmacro
.
define-syntax
Macro-by-Example macros
cheaply.
...
.
defmacro
natively supported (most implementations)
let-
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 section Macros That Work, section Syntactic Closures, and section 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.
(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)
...
) is
the pattern or template that appears to its left.
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.
(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
.
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
.
This section defines the concepts and data types used by the syntactic closures facility.
set!
special form is also a form. Examples of
forms:17 #t car (+ x 4) (lambda (x) x) (define pi 3.14159) if define
symbol?
. Macro transformers rarely distinguish symbols from
aliases, referring to both as identifiers.
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) ...))))
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.
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)
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.
(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 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).
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:
build-
procedures in `output.ss'
expand-syntax
(the expander)
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).
(require 'fluid-let)
Syntax: fluit-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.
(require 'oop)
or (require 'yasos)
`Yet Another Scheme Object System'. 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].
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.
Syntax: define-operation (
opname self arg ...)
default-body
Defines a default behavior for data objects which don't handle the operation opname. The default 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".
A default print
operation is provided which is just (format
port obj)
(See section Format) for non-instances and prints
obj preceded by `#<INSTANCE>' for instances. Note that
print
is also defined in the debug
module (See section Debug)
and these will conflict.
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 Collections) may override the default in an obvious way.
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 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
.
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"
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.
(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 (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
Go to the previous, next section.