279: In(tro)spection Protocol

by Artyom Bologov

Status

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-279@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

Abstract

Interactive REPL-driven systems (that most Schemes are) need a way to get detailed information on a given piece of data. Inspectors, as these are conventionally called. This SRFI defines a basic protocol for inspectors, consisting of two procedures: inspect-properties and inspect-describe. Some suggestions for standard and popular types’ inspection are also provided.

Issues

??? Optional section that may point out things to be resolved. This will not appear in the final SRFI.

Table of contents

Rationale

Inspectors, graphical or textual, have a relatively long history in Lisps. These go as far as seventies, with Maclisp having a DESCRIBE function, and Medley Interlisp environment providing an INSPECT function, among other introspection facilities of its programming system.

Outside the Lisp family, these tools have different names:

Inspectors are cohabitant with REPLs (read-eval-print loops), these having a long history in Lisps too: interactive inspection of the object implies that the environment is interactive and REPL-like; and having a REPL with arbitrary computation results implies that one might want to drill down on these results in detail.

In Scheme in particular, the need for inspectors is highlighted by multiple SRFIS: SRFI 102: Procedure Arity Inspection and SRFI 191: Procedure Arity Inspection highlight the need for procedure inspection; SRFI 69: Basic hash tables and a whole family of hash table SRFIs provide “introspection” / “reflection” procedures to get meta-information on hash tables; while SRFI 99 and R6RS explicitly name their record-type-querying procedures that: inspection. However, all of these efforts are dispersed around and isolated into distinct type-specific SRFIs.

Scheme needs generic inspectors. This SRFI provides a protocol and pointers that interactive inspectors for arbitrary data structures can be build from.

inspect-properties and inspect-describe are two procedures provided by this SRFI. inspect-properties allows previewing the structured key-value (meta)data about an object, while inspect-describe provides simple, human-readable description of an object for display in the REPL or UI (user interface). A list of useful properties per data type is provided for ease of implementation and consolidation of inspectors. Sample implementation is also provided for multiple implementations.

Specification

There are only two procedures in this SRFI: inspect-properties and inspect-describe. These are enough to implement a rich inspector. inspect-properties provides structured data to build the body of inspector from, while inspect-describe is useful in building a quick glance section or UI header. A potential and conventional inspector UI can thus look like

UI doodle, showing “The Inspector” screen. On it, the header is “(display obj);” text under it starts with “(inspect-describe obj)” and continues with unintelligible doodles (actually GESS stenography;) and the really badly drawn table having an “(inspect-properties obj)” header. Some properties follow, one of them is a link.
Inspector UI with the procedures used to fill it

procedure (inspect-properties object) → alist

This procedure returns a non-dotted alist of inspect properties for the object. These range from internal implementation details to standard queries to alternative formats to inlined element listings.

“Non-dotted alist” means an alist where the value of the pair is not its cdr, but rather a second element of the list (cadr). This restriction is in place to allow extending the returned list of properties with those beyond the key-value pair. Say, by adding UI display details or property setters as third and other elements in the entry.

It is recommended that implementations preserve read/write invariance and thus only use properly write-able data in property keys and values. Having that, one can easily use standard output facilities in UIs without the fear of breaking something. And store the inspection properties elsewhere. Say, as alternative to memory dumps for forensic investigation.

While properties like exact? and input-port? might be useful, they are not specified. Mainly because the object can be queried for these directly. Most other properties specified here cannot be queried directly or are inherent properties of the object.

In case the implementation has no information for a given property, this property should be omitted instead of using some dummy value like #f. This is to ensure that no field is confusing the inspector user about its presence.

Lastly, a note on notation: Property+value listings have special notation, which, although considered intuitive by the author, needs explanation:

name → type
Property named by name (likely symbol) with value having the type type. I.e. a list like (name value-of-type)
0..N → type
type-d elements of the sequence as properties in the property list. I.e. ((0 1) (1 "hello") (2 #\null)) inlined into property list.
field… → type
A key-value version of the above indexed inlining.
[type] (to the right of the → arrow)
A list of elements of type type.
type1 | type2 (to the right of the → arrow)
A value of either type type1 or type2.

Object properties

id → integer
Unique ID of the object, likely matching its memory location, garbage collector tag, or hash, whenever suitable.
location → implementation-specific
Representation of the memory location the object resides in.
size → integer
Amount of memory occupied by the object, in bytes.
type → implementation-specific
Type of the object, whenever inferrable.
write, display → string
Representations of the object as written by standard procedures.

Number (number?, complex?, real?, rational?, integer?) properties

imag-part → real, real-part → real
Parts of the complex number.
numerator → real, denominator → real
Parts of the rational number.
real-sign → integer, real-base → integer, real-exponent → integer, real-mantissa → integer
Parts of an exponentially-encoded real number. Sign is one of -1, 0, 1. Exponent and mantissa as per IEEE 754. Base is the numeric base the exponential representation relies on (almost always equal to 2). “Real-significand” can be provided as an additional “real-mantissa” alias.
real-exponent-length → integer, real-mantissa-length → integer
Length of respective parts of a real number. Given that all real? numbers have signs, real-sign-length is unnecessary, but can be added if implementors wish so.
real-precision → integer
The number of decimal places the type of the number can guarantee the exactness of.
fixnum-most-positive → integer, fixnum-most-negative → integer
For fixnums, the highest and lowest representable numbers.
flonum-most-positive → integer, flonum-most-negative → integer, flonum-least-positive → integer, flonum-least-negative → integer
For flonums, the highest / lowest / largest / smallest representable numbers.
integer-length → integer
Number of bits occupied by the integer.
integer-object → object
The object matching the ID represented by the integer.
integer->char → object
Character corresponding to the integer, whenever possible.
display-2, display-8, display-16 → string
Different representations of the number, as per number->string. Hexadecimal reals whenever supported by the implementation. “display-10” is not provided as synonymous with general object “display.”

Boolean (boolean?) properties

boolean->integer → integer
Numeric representation of the boolean used by the implementation. While quite obscure, this property might still be useful in deep implementation inspection. An example case: for an implementation written in BASIC boolean->integer for #t is -1 and not 1.

Pair (pair?) properties

Pairs in this section are implied to be non-null? lists, possibly dotted, possibly circular.

car, cdr → object
Components of the pair.
last → object, last-pair → pair
As per SRFI 1.
dotted-last → object
Last element of the dotted / improper list.
length → integer, length+ → integer
Length of the list (if the pair is one) and the length of potentially circular list.
list->string → string, list->vector → vector
Conversions of the pair, if it’s a convertible (non-dotted, non-circular) list.
0..N → object
Elements of the list inlined into properties. In case the list is dotted, the last element resides in “dotted-last” (above).
field… → object
In case the pair is an alist, its pairs are inlined into properties.

Symbol (symbol?) properties

symbol->string → string
Symbol name / string representation.
symbol-library → library
Library the symbol was imported from, whenever known. Can be a list naming the library or the library object.
symbol-value → object
Value bound to the symbol.
symbol-exported? → boolean
Whether the symbol is exported from the library it belongs to. While seemingly meaningless as per R7RS, it accommodates “internal” symbols akin to those of Common Lisp.
symbol-properties → list
List of properties attached to the symbol, whenever present.

Character (char?) properties

char->integer → integer
Integer representation of the character.
digit-value → integer
For characters representing digits, their numeric values.
char-name → string
Human-readable name of the character, e.g. “MALE WITH STROKE AND MALE AND FEMALE SIGN.”
char-category → symbol
Unicode codepoint category, e.g. “Ll.”
char-limit → integer
The maximum char value representable by the implementation.
char-macro → implementation-specific
Whenever the char has a macro / syntax / dispatcher bound to it.
char-alphabetic?, char-numeric? etc. → boolean
For all standard predicates.

String (string?) properties

string->symbol, string->list, string->vector, string->utf8, string->number
As per the standard
string-length → integer, string-byte-length → integer
Length of the string (as per the standard, in characters;) and length of the string in bytes.
file-exists? → boolean
Whenever the string represents a file path, whether the path exists. Other file-related properties fit into string properties too.
0..N → character
Characters of the string, inlined under their indices.

Vector (vector?) properties

vector-length → integer
Number of elements in the vector.
vector->list, vector->string
As per the standard.
0..N
Indexed elements of the vector, inlined into the properties.

Bytevector (bytevector?) properties

utf8->string
Bytevector represented as a string, whenever possible.
0..N → u8 | byte | integer
Elements of the bytevector, inlined into properties.

Port (procedure?) properties

port-open? → boolean
Whether the port is currently open.
port-direction → symbol
input, output, or both. While reachable with specific predicates, it’s too convenient to omit.
port-type → symbol
textual, binary, or an implementation-specific value
port-file → string, port-file-descriptor → integer
File the port is writing / reading from, if any.
port-line → integer, port-column → integer, port-position → integer
Position in the port, line/column-wise or character-wise.
port-encoding → symbol
Implementation-provided name of the port encoding.
port-buffer → implementation-specific
Representation of port output / input state.
get-output-string → string, get-output-bytevector → bytevector
Results of respective standard functions, for string and bytevector output ports.

Procedure (procedure?) properties

procedure-name → symbol | string
Likely a symbol that the procedure is currently bound to, whether local or global.
procedure-arity-mask → integer, procedure-arity → arity
As per SRFI 191 and SRFI 102 respectively. While both of these were withdrawn, they serve and important purpose: allowing basic yet essential introspection of procedures.
procedure-arglists → [list | symbol]
List of possible argument lists of the procedure. Procedures can be defined via case-lambda and thus have multiple arities / arglists. The symbol value can happen if the procedure accepts rest argument only, i.e. (lambda arg ...). Implementations are encouraged to only return standard-shaped arglists (required + rest), but may return implementation-specific extended lambda lists.
procedure-argument-types → [implementation-specific], procedure-return-types → [implementation-specific]
Whenever the implementation stores procedure types.
procedure-file → string, procedure-line → integer
Location of procedure definition.
procedure-lambda → list
lambda or case-lambda expression reproducing the procedure logic.
procedure-closure → alist
An alist of variables (symbols or numbers or implementation-specific identifiers) the procedure was closed over, and their values.
procedure-disassembly → implementation-specific
Bytecode, machine code, or an otherwise processed representation of the procedure.
procedure-tag → object
As per SRFI 229: Tagged Procedures or SRFI 259: Tagged procedures with type safety.

Record properties

record-rtd, record-type → rtd or another implementation-specific representation
Representation of the type the record is an instance of.
field… → object
symbol or integer indexed fields with their respective values

In case the returned record type is a record type descriptor (RTD) from R6RS / SRFI 99 or an equivalent, it might be inspectable, too. Possible fields (rtd can be replaced by record-type or other suitable phrase):

rtd-name → symbol
Name of the record type.
rtd-field-names → [symbol], rtd-accessors → [symbol], rtd-mutators → [symbol | #f]
Lists of field metadata, in the order of field definition.
rtd-types → [implementation-specific]
Types of record fields, whenever accessible.
rtd-predicate, rtd-constructor → symbol
Record-related procedures.
rtd-methods, rtd-interfaces, rtd-class, rtd-superclasses, rtd-vtable, rtd-prototype → implementation-specific
For implementations that represent records as objects and record types as classes. Other OOP properties are encouraged, whenever present.
rtd-parent → implementation-specific
The RTD / record type this RTD inherits from

Other R6RS record inspection properties can be included.

Error / exception (error-object? and other exceptions) properties

error-object-message → string, error-object-irritants → list
As per R7RS error section.
error-continuable? → boolean
Whether the error is continuable. While this goes against the general “no predicates” ethos of this SRFI, this property is not accessible from the outside world and thus needs special inclusion.
error-rtd, condition-rtd → rtd or another representation of error type
Record type descriptor, like for records. Can be a separate error-specific thing with separate (ertd-, error-record-type-) naming and properties.

Hash table (hash-table?) properties

There are several versions of hash tables in Scheme, with SRFI 69: Basic hash tables being the most prevalent. And with SRFI 126: R6RS-based hashtables and R6RS itself being an important influence. Naming of properties in this sections can rely on either hash-table or hashtable convention. But the hash-table convention is recommended (and used in this specification). Providing properties with both conventions is an option, too.

hash-table-equivalence-function, hash-table-hash-function → symbol
Names of procedures defining hash table behaviors.
hash-table-size → integer
Number of entries in the hash table.
hash-table-weak?, hash-table-mutable? → boolean
Boolean properties for prominent variations of hash tables.
hash-table-weakness → 'key | 'value | 'both
The part of entries that the weakness of the table depends on, if any.
key… → object
Keys and values in the hash table, essentially converted to properties alist.

Hash table effective size, rehash threshold, and rehash multiplier are not listed due to their relative obscurity and lack of implementation practice.

Numeric vectors (s8vector? etc.) properties

Defined in SRFI 4: Homogeneous numeric vector datatypes, these are too important to avoid inspecting them. In this section, a notation of “TAG” representing vector tag (s8, f64 etc.) is used in property names.

vector-tag → 's8 | 'u8 | 's16 | 'u16 | 's32 | 'u32 | 's64 | 'u64 | 'f32 | 'f64
The type TAG of the vector. Can also be added to vector properties and bytevector properties, whenever provable / stored / available.
TAGvector-length → integer, TAGvector->list → [number]
As per SRFI 4.
0..N
Indexed elements of the vector, inlined into the properties.

Box (box?) properties

Specified in SRFI 111: Boxes, boxes / cells / atoms are useful modifiable containers for a single value.

unbox → object
The value inside the box.

Related SRFIs, like SRFI 195: Multiple-value boxes and SRFI 189: Maybe and Either are not mentioned here, but may as well be supported by implementations providing them.

Character set (char-set?) properties

Defined in SRFI 14.

char-set-size → integer
Number of characters in the charset.
char-set->list → [character], char-set->string → string
As per SRFI 14.
char-set-name → symbol
Name of one of the pre-defined character sets, like char-set:lower-case.
char… → char
Listing of all characters in the set, as self-valued properties.

Set and bag (set?, bag?) properties

set-element-comparator → symbol
Name of the procedure used to compare elements in the set.
object… → object
Set / bag elements, inlined into properties, keyed and valued by themselves.

Bag properties are suggested to have only one pair in order to not duplicate work and to delegate everything to set properties. However, implementations may freely add more bag-specific properties.

bag->set → set
Used for bag inspection, delegated to set inspection.

Library properties

While not represented in standard type system, library is a valid concept deserving inspection.

library-name → list
Proper list naming the library.
library-imports → list, library-exports → list
Lists mirroring syntax of library declaration fragments.
library-body → list
List of forms library body consists of. Might include the include-d forms and is otherwise implementation-dependent.

Environment properties

Same as library, the return value of environment and the like is not first-class. However, these are useful enough to prompt inspection.

environment-libraries → [library | pair]
List of libraries imported into the environment.
environment-symbols → [symbol]
List of all symbols transitively exported from libraries in the environment.

procedure (inspect-describe object [port]) → unspecified

Prints a human-readable and maximally useful description of the object to port (or current-output-port). (The exact definition of “useful” output is left to the implementor.) Example of opinionated output from the author’s trivial-inspect Common Lisp library, adapted to Scheme:

Hash-table [equal?, 4]
 ("KARINA" . "Rocket puncher")
 ("WINTER" . "Armamenter")
 ("GISELLE" . "Xenoglossy")
 ("NINGNING" . "E.D. Hacker")

Implementation

The source for the sample implementation can be found in the Github repo or in this .tgz file.

Acknowledgements

??? credit where it is due. For example, please consider acknowledging people on the SRFI mailing list who have contributed to the discussion.

© 2026 Artyom Bologov.

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.


Editor: Arthur A. Gleckler