260: Generated Symbols

by Marc Nieper-Wißkirchen

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

Abstract

This SRFI defines the procedure generate-symbol. Each time it is invoked, the procedure returns a new symbol whose name cannot be guessed. The returned symbol is a standard symbol for all purposes; it obeys write/read invariance and it is equal to another symbol if and only if their names are spelt the same.

Issues

None at present.

Rationale

A unique object is a Scheme value that is not equal (usually with respect to eqv?) to existing (or potentially existing) objects. Unique objects have many use cases, for example as private keys in data structures or to uniquely mark other values.

Scheme has many ways to create such unique objects. For many purposes, an expression like (string #\m) can be used to create a unique object. To improve type safety, alternatively, a constructor of a record type with at least one mutable field can be invoked.

There are circumstances where such unique objects, may they be strings or records, cannot be used because an object of a particular type, specifically a symbol, is needed. In the case of Scheme, this applies to its syntax system. An identifier is a syntax object that is a wrapped symbol (see R6RS). To generate unique identifiers with a given wrap, unique symbols (and not strings, records, etc.), therefore, have to be generated.

The procedure generate-symbol defined in this SRFI provides a means to generate unique symbols.

Alternative proposals like SRFI 258 provide so-called uninterned symbols that can also be used for this purpose (at least after amending the definition of a syntax object). Introducing uninterned symbols into the standard, however, would weaken the semantics of symbols. Two symbols would no longer be identical (in the sense of eqv?) if and only if their names are spelt the same way.

Uninterned symbols have another disadvantage in that they defeat write/read invariance. Yet, some kind of write/read invariance is likely needed when uninterned symbols are used in identifier names so that expanded Scheme code can be serialised (e.g. for separate AOT expansion/compilation).

As names for generated symbols can be generated lazily, the alleged advantage of uninterned symbols — that no names have to be generated — is a bogus one. A name for a generated symbol only has to be generated for use cases that need the unique name from symbol->string, e.g. for write/read purposes, but these use cases are not covered by uninterned symbols anyway.

Generated symbols need no new lexical syntax. The notion of a Scheme datum value does not change; a Scheme datum value remains a Scheme value that obeys write/read equivalence (up to equal?).

Specification

Libraries

The following binding is exported by the (srfi :260 generate-symbol) library:

Procedure: generate-symbol [pretty-name]

The optional argument, pretty-name, must, if present, be a string.

Returns a symbol that is not identical to any existing or potentially existing symbol, for all practical purposes.

Note: This means that for every Scheme expression ⟨expression⟩ whose (possibly non-deterministic) value does not depend on the result of symbol->string applied to the result gensym of a previous invocation of generate-symbol, its value is not equal (in the sense of equal?) to the value of (symbol->string gensym) for all practical purposes.

The implementation may use the argument pretty-name for debugging or pretty-printing purposes.

(let ([s (generate-symbol)])
  (symbol=? s s)) ; => #t
(let ([s1 (generate-symbol)] [s2 (generate-symbol)])
  (symbol=? s1 s2)) ;=> #f
(let ([s1 (generate-symbol "g1")] [s2 (generate-symbol "g1")])
  (symbol=? s1 s2)) ;=> #f
(let ([s1 (generate-symbol "g1")] [s2 'g1])
  (symbol=? s1 s2)) ;=> #f
(let ([s (generate-symbol "g1")])
  (string=? (symbol->string s) "g1")) ;=> #f
(let ([s (generate-symbol)])
  (symbol=? (string->symbol (symbol->string s)) s)) ;=> #t

Implementation

A native implementation does not need to create a name for the symbol returned by an application of generate-symbol immediately but only lazily when symbol->string is applied to the symbol for the first time. If symbol->string is never applied as will likely be the case in many uses of generated symbols, no name has to be created. Symbols generated by generate-symbol can therefore replace uninterned symbols known from some Scheme implementations.

When an implementation has to create a name, it can simply choose a randomly generated name so that the entropy of the random variable described by (lambda () (symbol->string (generate-symbol))) is sufficiently high (e.g. 122 bits as in the case of Version 4 UUIDs but note that this SRFI prescribes no particular form of the symbol name).

The portable sample implementation creates random names eagerly and uses /dev/random for this purpose.

Acknowledgements

This SRFI was created in response to SRFI 258: Uninterned Symbols.

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


Editor: Arthur A. Gleckler