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-229@nospamsrfi.schemers.org
. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
This SRFI defines tagged procedures, which are procedures
that are tagged with a Scheme value when created through the
syntax lambda/tag
and case-lambda/tag
. The
value of the tag of a procedure can be retrieved
with procedure-tag
, and the
predicate procedure/tag?
discerns whether a procedure is
tagged.
The only way to interact with an ordinary Scheme procedure, besides
applying predicates like eqv?
or procedure?
to
it, is to call it. Some applications, however, have the need to
associate data with a procedure that can be retrieved without calling the
procedure. For an R7RS system, this can be achieved in principle by
associating the data through a weak hash table. For an R6RS system, this
is not possible because R6RS does not expose a coarse-enough equivalence
predicate for procedures. But even for an R7RS system, such an approach
is not straightforward. Firstly, weak hash tables are not necessarily
available and, secondly, even for an R7RS system, procedure equivalence
is not trivial because the expression types that create
procedures, lambda
and case-lambda
, are not
necessarily generative.
Therefore, this SRFI defines a simple interface that can be used to create tagged procedures, to detect the existence of a tag, and to retrieve it. Through this mechanism, even R6RS procedures can be given an identity that can be tested in Scheme code.
The mechanism defined in this SRFI can be used to implement procedure properties, documentation strings, source location information, and other things in Scheme.
Scheme records as defined by R6RS or R7R7 cannot be called as procedures, but this SRFI can be used to implement an alternative record system where the record instances are procedures whose actual fields are stored in a private record as the procedure's tag.
In order to not prevent certain optimizations, the procedure tags defined through the facilities presented here are immutable. In other words, the Scheme object used as a tag is fixed at procedure creation time. If an application needs mutable fields attached to a procedure, a Scheme object that is itself mutable (like a pair, a string, a vector, or a record with mutable fields) has to be used as a tag.
Contrary to proposals that conceptually attach properties to every procedure, this SRFI gives implementations the freedom to have disjoint types of tagged and untagged procedures, both non-empty. In particular, this SRFI can be implemented at zero cost for the general case of untagged procedures.
In fact, in the context of SRFI 229, one shouldn't think of procedures falling in just two classes, untagged and tagged ones (note that all procedures may be implementable as tagged ones), but falling into infinitely many classes, one for each type of its tag. So one can really think of them as callable structs, where the struct is in the tag. (See MIT/GNU Scheme's apply hooks for an example.)
Future SRFIs are expected to build on SRFI 229 to provide a procedure type with an extensible tag field, allowing independent composition.
MIT/GNU Scheme Scheme defines application hooks and entities, which are objects that can applied like procedures. As a demonstration of the generality of the primitives defined by this SRFI, we give a portable sample implementation of application hooks and entities on top of this SRFI:
(define-library (application-hook) (export make-apply-hook apply-hook? apply-hook-procedure set-apply-hook-procedure! apply-hook-extra set-apply-hook-extra! make-entity entity? entity-procedure set-entity-procedure! entity-extra set-entity-extra!) (import (scheme base) (srfi 229)) (begin (define-record-type apply-hook-tag (make-apply-hook-tag procedure extra) apply-hook-tag? (procedure apply-hook-tag-procedure apply-hook-tag-set-procedure!) (extra apply-hook-tag-extra apply-hook-tag-set-extra!)) (define (make-apply-hook proc obj) (let ((tag (make-apply-hook-tag proc obj))) (lambda/tag tag arg* (apply (apply-hook-tag-procedure tag) arg*)))) (define (apply-hook? obj) (and (procedure/tag? obj) (apply-hook-tag? (procedure-tag obj)))) (define (apply-hook-procedure hook) (apply-hook-tag-procedure (procedure-tag hook))) (define (set-apply-hook-procedure! hook proc) (apply-hook-tag-set-procedure! (procedure-tag hook) proc)) (define (apply-hook-extra hook) (apply-hook-tag-extra (procedure-tag hook))) (define (set-apply-hook-extra! hook obj) (apply-hook-tag-set-extra! (procedure-tag hook) obj)) (define-record-type entity-tag (make-entity-tag procedure extra) entity-tag? (procedure entity-tag-procedure entity-tag-set-procedure!) (extra entity-tag-extra entity-tag-set-extra!)) (define (make-entity proc obj) (let ((tag (make-entity-tag proc obj))) (define f (lambda/tag tag arg* (apply (entity-tag-procedure tag) f arg*))) f)) (define (entity? obj) (and (procedure/tag? obj) (entity-tag? (procedure-tag obj)))) (define (entity-procedure hook) (entity-tag-procedure (procedure-tag hook))) (define (set-entity-procedure! hook proc) (entity-tag-set-procedure! (procedure-tag hook) proc)) (define (entity-extra hook) (entity-tag-extra (procedure-tag hook))) (define (set-entity-extra! hook obj) (entity-tag-set-extra! (procedure-tag hook) obj))))
When invoked on two objects, the eqv?
procedure
returns #f
if one object is a tagged procedure and
the other object is a non-procedure or a procedure that is not
tagged, or if both objects are tagged procedures but tagged with
values such that eq?
would return #f
when invoked on them.
Note: Note that in an R7RS system, all
procedures are conceptually tagged with a storage location and
that eqv?
returns #t
when invoked on
procedures whose location tags are equal. As each tagged
procedure (in the sense of this SRFI) is also a procedure in the
sense of the Scheme reports, tagged procedures inherit this
behavior in R7RS systems.
On the other hand, the syntax and procedures of this SRFI can be used to give procedures also in R6RS systems an identity that can be discerned through Scheme procedures:
(define-syntax lambda/id
(syntax-rules ()
((lambda/id formals body)
(lambda/tag (list 'tag) formals body))))
(define lambda/id=?
(lambda (f g)
(assert (procedure/tag? f))
(assert (procedure/tag? g))
(eq? (procedure-tag f) (procedure-tag g))))
(define p (lambda/id (x) x))
(lambda/id=? p p)#t
(case-lambda/tag expression
(formals body) …)
syntax
Semantics: First, expression
is evaluated to
obtain a value. Then a procedure is returned that accepts a variable number of
arguments, that is lexically scoped in the same manner as a procedure
resulting from a lambda
expression, and that
is tagged with the obtained value. When the
procedure is called, it behaves as if constructed by
a case-lambda
expression with the
same formals
and bodies
.
(lambda/tag expression
formals body)
syntax
Semantics:
Operationally equivalent to (case-lambda/tag expression
(formals body))
.
(procedure/tag? obj)
procedureReturns #t
if obj
is a tagged
procedure and #f
otherwise.
Note: It is unspecified whether a procedure created by
a lambda
or case-lambda
expression or
returned by a procedure of one of the standard libraries or defined
by syntax of one of the standard libraries is tagged. In
case it is tagged, the tag value is unspecified.
(procedure-tag proc)
procedureIt is an error if proc
is not a tagged procedure.
Returns the tag of proc
.
(define f (lambda/tag 42 (x) (* x x))) (procedure/tag? f)#t (f 3) 9 (procedure-tag f)42 (define f* (lambda/tag 43 (x) (* x x))) (eqv? f f*)#f (define g (let ((y 10)) (lambda/tag y () (set! y (+ y 1)) y))) (procedure-tag g)10 (let ((y 9)) (procedure-tag g))10 (g)11 (procedure-tag g)10 (define h (let ((box (vector #f))) (case-lambda/tag box (() (vector-ref box 0)) ((val) (vector-set! box 0 val))))) (h 1) (vector-ref (procedure-tag h) 0))1 (h)1
The following is a portable R7RS implementation. It is fully conformant, but tagged procedures will never be garbage-collected.
(define-library (srfi 229) (export case-lambda/tag lambda/tag procedure/tag? procedure-tag) (import (scheme base) (scheme case-lambda)) (begin (define *tagged-procedures* '()) (define key (list 'key)) (define make-procedure/tag (lambda (tag proc) (define f (case-lambda ((arg) (if (eq? arg key) tag (proc arg))) (arg* (apply proc arg*)))) (set! *tagged-procedures* (cons f *tagged-procedures*)) f)) (define-syntax case-lambda/tag (syntax-rules () ((case-lambda/tag expr (formals body1 ... body2) ...) (make-procedure/tag expr (case-lambda (formals body1 ... body2) ...))))) (define-syntax lambda/tag (syntax-rules () ((lambda/tag expr formals body1 ... body2) (make-procedure/tag expr (lambda formals body1 ... body2))))) (define procedure/tag? (lambda (f) (and (memv f *tagged-procedures*) #t))) (define procedure-tag (lambda (f) (f key)))))
Scheme implementers should replace the above implementation by a more efficient one where the tag value is stored like a closure variable.
A portable R6RS implementation is not possible because procedures in R6RS have no identity (exposed via Scheme predicates).
Thanks to the participants on the mailing list, and to Arthur A. Gleckler, who pointed out MIT/GNU Scheme's application hooks to me.
Some Scheme systems like Kawa implement procedure properties, which can be used to implement this SRFI.
Thanks to Göran Weinholt for implementing this SRFI during its draft period natively in his Loko compiler.
© 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.