SRFI 279: In(tro)spection Protocol

# 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.

- Received: 2026-08-11
- 60-day deadline: 2026-10-10
- Draft #1 published: 2026-08-11

## 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
- Specification
  - procedure (inspect-properties object) → alist
    - Object properties
    - Number (number?, complex?, real?, rational?, integer?) properties
    - Boolean (boolean?) properties
    - Pair (pair?) properties
    - Symbol (symbol?) properties
    - Character (char?) properties
    - String (string?) properties
    - Vector (vector?) properties
    - Bytevector (bytevector?) properties
    - Port (procedure?) properties
    - Procedure (procedure?) properties
    - Record properties
    - Error / exception (error-object? and other exceptions) properties
    - Hash table (hash-table?) properties
    - Numeric vectors (s8vector? etc.) properties
    - Box (box?) properties
    - Character set (char-set?) properties
    - Set and bag (set?, bag?) properties
    - Library properties
    - Environment properties
  - procedure (inspect-describe object [port]) → unspecified
- Implementation
- Acknowledgements
- Copyright

## 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:

- memory debuggers (low-level byte glance),
- reflection (programmatic access to (usually) object metadata),
- serialization / serializability (storage-oriented data description),
- and introspection.

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

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.

## Copyright

© 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.