SRFI-259
Tagged procedures with type safety
TOC »
Description
An implementation of SRFI-259 in Chicken. The SRFI is a modification to SRFI-229.
This library allows for procedures (closures and even procedures imported from other libraries) to be tagged with data. A procedure object can be tagged by multiple different tagged procedure constructors without overwriting previous data. This can be used to implement object systems or to allow finer-grained predicates on procedures.
There is something similar to this SRFI, called extend-procedure, in the Chicken standard libraries. There are some important differences:
- extend-procedure will mutate previously extended procedures. This SRFI is persistent, so adding new data to a procedure will return a new procedure, and will not mutate the old one.
- This SRFI encapsulates data so that multiple tagging systems can tag procedures without overwriting data previously tagged to a procedure. extend-procedure is low level.
There is also procedure-decoration, which is similar to this SRFI.
This implementation uses integer-map to store tagged data, meaning that a procedure could be tagged with large amounts of protocols efficiently.
Authors
SRFI author: Daphne Preston-Kendal
Egg author/maintainer: Peter McGoron
Repository
https://software.mcgoron.com/srfi-259-egg.git
(my website is currently broken, but git clone will still work)
Requirements
API
The following is copy-pasted from the SRFI with minor changes.
- (define-procedure-tag constructor-name predicate-name accessor-name)syntax
define-procedure-tag is a define form. The subforms are all identifiers.
define-procedure-tag binds the identifiers constructor-name, predicate-name, and accessor-name to three procedures for a newly-created tagging protocol.
The constructor name is bound to a constructor procedure of two arguments, tag and proc. proc must be a procedure, referred to as the underlying procedure. When invoked, the constructor procedure returns a new procedure object which has identical arity and behaviour as the underlying procedure, but is tagged in the new tagging protocol with the object tag. If the underlying procedure is already a tagged procedure in some protocol or protocols, the new tag is added in the newly-returned procedure to the existing ones, with the given tag; if one of those protocols is the created tagging protocol, the tag value for this protocol is effectively replaced with the new tag value. If a non-procedure is passed to the constructor, a condition of kind exn and type is raised.
The procedure object returned from the constructor procedure has a fresh location tag and is distinct from all existing procedures in the sense of the eqv?, eq?, and equal? procedures.
The predicate-name is bound to a predicate procedure of one argument. When this procedure is invoked, it returns #t if its argument is a procedure object that was created by a call to the constructor procedure, or #f otherwise.
The accessor-name is bound to an accessor procedure of one argument. Its argument is a procedure object created by a call to the constructor procedure. If the argument is not a procedure, a condition of kind exn and type is raised. If the argument is not tagged in the protocol, then a condition of kidn exn and assertion is raised. The accessor procedure returns the value of the tag which was provided when the constructor procedure was called.
define-procedure-tag is generative: every evaluation causes the creation of a new procedure tagging protocol, and the constructor, predicate, and accessor procedures created do not operate on tagged procedures with the protocol from previous or future evaluations of the same define-procedure-tag form.
Procedure tagging protocols do not create a disjoint type: the procedure? predicate answers #t on tagged procedures.
Extensions
These are exported from (srfi 259 extensions), which also exports define-procedure-tag.
- (lambda/this this formal body ...)syntax
this is an identifier. formal is a lambda formal.
Create a procedure such that this is bound to the location of the current procedure. If the procedure is tagged by a constructor made by define-procedure-tag, then that new procedure will have this bound to the new procedure.
Here is an example of its usage:
(import (srfi 259 extensions)) (define-procedure-tag tag-foo tag-foo? get-tag-foo) (define f (lambda/this this (x) (if (tag-foo? this) (+ x (get-tag-foo this)) x))) (f 10) ; => 10 (define g (tag-foo 10 f)) (g 10) ; => 20 (f 10) ; => 10
- (define/this this (name . formal) body ...)syntax
Equivalent to
(define name (lambda/this this formal body ...))
- procedure/this? objprocedure
Returns true if obj was created by lambda/this, and false otherwise.
Examples
The following is copy-pasted from the SRFI with minor changes.
The following code implements the core of the T object system (also known as the ‘operations’, ‘YASOS’, or ‘Scheming with Objects’ system). The exports are object, object?, operation, operation?
(define-procedure-tag make-object object? object-vtable) (define-syntax object (syntax-rules () ((_ proc-expr ((operation . formals) body_0 body_1 ...) ...) (letrec* ((proc (cond (proc-expr) (else (lambda ignored (error "object not callable"))))) (obj (make-object (list (cons operation (lambda formals body_0 body_1 ...)) ...) proc))) obj)))) (define (find-method obj op) (cond ((assv op (object-vtable obj)) => cdr) (else #f))) (define operation? (object (lambda (x) #f) ((operation? self) #t))) (define-syntax operation (syntax-rules () ((_ default-expr ...) (letrec* ((default default-expr) (op (object (lambda (obj . args) (cond ((and (object? obj) (find-method obj op)) => (lambda (method) (apply method obj args))) (else (apply default obj args)))) ((operation . formals) body_0 body_1 ...) ...))) op))))
Issues/Future Directions
- Once a procedure is tagged in a protocol, it will always remain tagged in that protocol. The tagged data can be removed but there is a small memory leak because the protocol tag will persist for all invocations of the function.
- Alists are probably faster when the average amount of protocols a procedure is tagged with is small. This is probably the case but this SRFI is too new to have any data on this.
- The SRFI is not finalized yet.
License
© 2025 Daphne Preston-Kendal.
© 2025 Peter McGoron.
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.
Version History
- 0.10.0
- add extensions (lambda/this, define/this, and procedure/this?)
- 0.9.2
- Fix segmentation fault errors, and make the form a define form
- 0.9.1
- Better error checking, document kinds of conditions raised
- 0.9.0
- Initial release