by Daniel Ziltener
This SRFI is currently in draft status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-263@nospamsrfi.schemers.org
. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
This SRFI proposes a "Self"-inspired prototype object system. Such an object system works by having prototype objects that are cloned repeatedly to modify, extend, and use them, and is interacted with by passing messages.
None at present.
Objects can be very useful for scenarios where an inheritance of functionality and a strong pairing between functionality and data is desirable. Due to its simple yet versatile concept, a prototype-based object system is a good fit for Scheme to fill that role.
The objects are based on closures. To send a message to that object, the closure is applied to the message selector (i.e., the slot name), followed by a number of arguments. The return value(s) of the message are returned from this application.
Objects contain slots, each of which is of one of three types:
Method slot procedures are called with at least two arguments, which should be called self and resend. If the message received any arguments, they are passed as additional positional arguments. Self is the object which received the message. Resend is a procedure which can be used to resend the message to further parents if the current method does not wish to handle the message. (See "Inheritance" for more information about this).
A typical method handler could thus look like this:
(lambda (self resend a b) (/ (+ a b) 2))
Every slot, regardless of its kind, can be associated with a setter method when it is created. Setter methods receive a single argument, and replace the value of the corresponding slot with this argument. Setter methods can be created automatically when a given slot is created, and are removed when the corresponding getter slot is removed (but not vice versa). Because of this, they are sometimes not considered to be slots, even though they are. See «Setters are Methods» for an example where this distinction is important.
Following the original concepts of object oriented programming, SRFI-263 uses messages to communicate with and between objects. To send a message to an object, the object has to be invoked with the target slot as first argument, with the slot's arguments appended as further arguments. Sending a new x
coordinate to a point
object looks like this:
(pointobj 'set-x! 50)
When a slot for a message is not found in the current object, all its parent slots are queried recursively, i.e. parent objects which don't know the slot query their parents recursively.
If no parent knows the slot, the original message receiving object is sent a message-not-understood message. If more than one parent knows the slot, the original message receiving object is sent an ambiguous-message-send message. See «Root Objects» for a documentation of those messages. By default, they signal an error.
Method slots can decide not to handle a message, but rather pass it up the inheritance tree for other handlers. For this purpose, they are passed a procedure commonly called resend
. See «Slots» for an explanation of method slots.
It is important to understand the difference between sending a message to an object and re-sending it to the object. When a message is just sent to an object, methods will get that object as the self
argument. When a message is resent, self
instead refers to the object it was originally sent to.
Since objects are created by sending a clone message to other objects, there has to be a kind of root object.
*the-root-object*
- classThis is the default root object. This should be used as the root of the object hierarchy in a program.
Root objects contain a number of slots by default.
clone
- messageReturn a clone of the message recipient. This creates a new object with a single slot, parent
, which points to the object cloned from.
mirror
- messageReturn a mirror object that contains various slots providing reflection information about the receiver.
add-value-slot! getter [setter] value
- messageAdd a new value slot to the recipient. The value of the slot can be retrieved with the getter message. If a setter message is given, that message can be used to change the value of the slot.
add-method-slot! getter [setter] proc
- messageAdd a method slot to the recipient. The slot procedure can be invoked with the getter message. The procedure will be invoked with a self
argument bound to the object that received the message, a resend
procedure to resend the message if the method does not want to handle it directly, and all further arguments passed to the message send.
If a setter message is given, it can be used to change the procedure.
add-parent-slot! getter [setter] parent
- messageAdd a parent slot to the recipient. Parent slots are searched for slots not found directly in the object. The setter message, if given, can be used to later change the value of the parent slot.
delete-slot! name
- messageDelete the slot named name
from the receiving object. This also removes the setter corresponding to name
, if any. Beware that the parents might contain the same slot, so a message send can still succeed even after a slot is deleted.
message-not-understood message args
- messageThis is received when the message message
with the arguments args
to the object was not understood. The root object just signals an error.
ambiguous-message-send message args
- messageThis is received when the message message
with arguments args
to the object would have reached multiple parents. The root object just signals an error.
Sending the mirror
message to an object returns that object's mirror. A mirror is a special class with slots that allow one to investigate the mirrored object.
has-ancestor object
- messageA predicate message that returns #t
if object is an ancestor of the mirrored object, and #f
if not.
immediate-slot-list
- messageThis message returns a list of slots in the receiving object. The elements of the list are lists with three elements each:
#f
value
, method
, or parent
full-slot-list
- messageThis message returns a list of slots in the receiving object and all its ancestors. The elements of the list are lists with three elements each:
#f
value
, method
, or parent
(srfi 263 syntax)
define-method (obj message self resend . args) body ...
- syntaxThis is syntactic sugar for the often-used idiom to define a method slot by sending an add-method-slot!
message with a message name and a lambda form with self
, resend
, and args
formals, and a body
. This shortens the following code:
(obj 'add-method-slot!
'average
(lambda (self resend a b)
(/ (+ a b) 2)))
to this:
(define-method (obj 'average self resend a b)
(/ (+ a b) 2))
define-object name (parent other-parents ...) slots ...
- syntaxThis is syntactic sugar for the typical actions of cloning an object from a parent object, and adding more slots.
other-parents
is a list of (name object)
lists, where each object is added as a parent slot named name
.
slots
is a list of slot specifications, either (getter value)
or (getter setter value)
for value slots, or ((name self resend args ...) body ...)
for method slots.
This allows for shortening the following example:
(define o (*the-root-object* 'clone))
(o 'add-value-slot! 'constant 'set-constant! 5)
(o 'add-method-slot! 'add
(lambda (self resend summand)
(+ summand (self 'constant)))))
into this:
(define-object o (*the-root-object*)
(constant set-constant! 5)
((add self resend summand)
(+ summand (self 'constant))))
Message names don't have any required type. They are only compared using eq?
. Because of this, any Scheme object can be used a message name. This means that it is possible to use a private Scheme value, e.g. a freshly allocated list, as a slot name. This can be used to keep slot names private, since it is not possible to create an object which is eq?
to such an object except by receiving a reference to that object.
Please follow the linguistic conventions described in the FAQ. In particular, please follow the conventions about the use of the terms must, must not, required, shall, shall not, should, should not, recommended, may, and optional defined by RFC 2119. This convention has been adopted by R7RS Small, too, in section 1.3.2 (PDF).
Also by convention, the phrase it is an error to X is used in Scheme documents to mean that X is not allowed, but to leave it up to the implementer to decide what to do in response. Implementations may signal an error, assign some new meaning to X, or fail catastrophically.
??? explanation of how it meets the sample implementation requirement (see process), and the code, if possible, or a link to it
Source for the sample implementation.??? credit where it is due. For example, please consider acknowledging people on the SRFI mailing list who have contributed to the discussion.
© 2025 Daniel Ziltener, Jorgen Schaefer.
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.