by Marc Nieper-Wißkirchen
This SRFI is currently in final status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-213@nospamsrfi.schemers.org
. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
If an identifier is imported more than once in a program or
library declaration, the identifier properties attached to the
imported bindings are merged. It is an error if two properties
with the same key but that were defined by different expansions of
the define-property
definition would be merged.
Note: R7RS already specifies that it is an error to import an identifier more than once with different bindings.
Using the define-property
definition described in
this SRFI, expand-time properties can be associated with
identifiers in a referentially transparent and lexically scoped way.
In many dialects of Lisps, symbols have attached properties. Each property associates a key with a value. Traditionally, identifiers in Lisp are the same as symbols and traditional Lisp macros transform Lisp data (usually represented by s-expressions). In particular, symbol properties can be used by one macro to record information about an identifier for use by another dependent macro.
In contrast to traditional Lisps, Scheme has hygienic macros and lexical scoping. The hygiene condition necessitates that identifiers are in general no longer symbols, so we want to attach properties to identifiers instead of symbols. To achieve lexical scoping of these properties, the properties have, in fact, to be attached to the binding of an identifier and become expand-time properties. Moreover, to preserve referential transparency, a property of an identifier has to be associated with the binding of a key that is itself an identifier instead of a symbol.
An implementation of this idea of identifier properties is this
SRFI, which describes the define-property
definition, which attaches a property to an identifier, and a
way for macro transformers to look up these properties.
Use cases are numerous. One could write a pattern matcher
together with a template engine akin to
the syntax-case
/syntax
pair so that
properties are attached to identifiers that represent pattern
variables for identification in the template elements. In fact,
one could portably implement syntax-case
and syntax
on top of the low-level macro
facility (srfi 211 low-level)
with this SRFI.
Another use case is the attachment of properties to record-type
descriptors for, say, the use in child record type definitions.
Or one could attach compile-time information to procedures for
optimization purposes at their use site, and so on.
In order to attach a property to an identifier binding, we have to create such a binding first:
(define computer "the-computer")
Moreover, we need a binding that can act as the property's key:
(define answer #f)
Attaching the property is then as simple as
(define-property computer answer (* 2 21))
As the property associates an expand-time value, we have to retrieve the property through a macro:
(define-syntax get-the-answer (lambda (stx) (capture-lookup (lambda (lookup) (syntax-case stx () ((_ x key) (let ((res (lookup #'x #'key))) (cond ((not res) #f) ((number? res) res) (else (error "not a number" res))))))))))
We then have the expansion (and thus evaluation) of
(get-the-answer computer answer) ⟹ 42
By the way, attaching the property hasn't disturbed the original binding:
computer ⟹ "the-computer"
If we shadow the binding of the key, we cannot access the property any more, making private interfaces possible:
(let ((answer #f)) (get-the-answer computer answer)) ⟹ #f (get-the-answer computer answer) ⟹ 42
The same is true if we change the meaning of the identifier the property is attached to:
(let ((computer "the-super-computer")) (get-the-answer computer answer)) ⟹ #f (get-the-answer computer answer) ⟹ 42
Finally, the attachment of properties is lexically scoped:
(let* () (define-property computer answer 43) (get-the-answer computer answer)) ⟹ 43 (get-the-answer computer answer) ⟹ 42
Like other definitions, the define-property
definition can either appear at the outermost level or in a body
where other definitions can appear.
(define-property identifier
key expression)
Syntax: Identifier
and key
are bound
identifiers. Expression
can be any expression.
Semantics:
The expression
is
evaluated at macro-expansion time and a new property associating
the binding of key
with
the resulting value is attached to the binding
of identifier
without
disturbing the existing meaning of the identifier in the scope
of that binding.
The scope of a property introduced
by define-property
is the entire body in which the
define-property
form appears or the outermost
level, except where it is replaced by a property for the
same identifier
and key
or where the
binding to which it is attached is shadowed.
Any number of properties can be attached to the same binding with different keys. Attaching a new property with the same name as a property already attached to a binding shadows the existing property with the new property.
Note: Contrary to other definitions, it is not an error to define the same property more than once in the same body.
(capture-lookup proc)
Capture-lookup
is used in macro transformers as
described below. When invoked, it returns a single value.
It is an error if proc
is not a
procedure accepting a single argument.
For backward compatibility, capture-lookup
must be
substitutable by the identity (lambda (proc) proc)
when used in macro transformers as described below.
If a transformation procedure returns the result of
invoking capture-lookup
on a
procedure proc
instead of returning a
syntax object when applied to the input of a macro use, the
transformer is re-entered by
invoking proc
with the single
argument lookup
.
Lookup
is a procedure accepting two
identifier arguments id
and key
.
The lookup
procedure returns the value
of the property of id
associated with
the binding of key
or #f
if
there is no such property. It is an error
if key
is not bound.
If id
is unbound, an error is signaled.
Rationale: The reason why it is enforced that an error
is signaled when id
is unbound is so that
macro transformers can test through the facilities provided by
this SRFI whether identifiers are bound (in the transformer
environment).
If an er-macro-transformer returns the result of
invoking capture-lookup
on a
procedure proc
instead of returning a
datum when applied to the input of a macro use, the transformer
is re-entered by invoking proc
with the
single argument lookup
.
The lookup
procedure is as in the case
of transformer procedures except that if it is called with a
symbol for the argument for id
or key
, the symbol behaves as if it
represents an identifier introduced at the macro use.
If an ir-macro-transformer returns the result of
invoking capture-lookup
on a
procedure proc
instead of returning a
datum when applied to the input of a macro use, the transformer
is re-entered by invoking proc
with the
single argument lookup
.
The lookup
procedure is as in the case
of transformer procedures except that if it is called with a
symbol for the argument for id
or key
, the symbol behaves as if it
represents an identifier introduced at the macro definition.
If a sc-macro-transformer returns the result of
invoking capture-lookup
on a
procedure proc
instead of a returning form when
applied to the input of a macro use, the transformer is
re-entered by invoking proc
with the
single argument lookup
.
The lookup
procedure is as in the case
of transformer procedures except that it accepts four
arguments id-env
,
id
, key-env
,
and key
,
where id-env
and key-env
are syntactic environments.
The bindings of id
and key
are then looked up in these
environments.
If a lisp-transformer returns the result of
invoking capture-lookup
on a
procedure proc
instead of returning a
datum when applied to the input of a macro use, the transformer
is re-entered by invoking proc
with the
single argument lookup
.
The lookup
procedure is as in the case
of transformer procedures except that it accepts symbols instead
of identifiers as arguments for id
or key
. The symbols
behave as if they represent identifiers introduced at the
macro use.
This SRFI is provided by the Unsyntax implementation of the Scheme programming language.
Chez Scheme
implements define-property
in
its (chezscheme)
library.
The author read about the define-property
definition in the Chez Scheme User's Guide. Some of the
wording was shamelessly stolen from the manual.
© 2020-2021 Marc Nieper-Wißkirchen.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.