Wing Hei Chan
Gref is a proof-of-concept implementation of generalized references for Racket. It is intended as a showcase for Racket’s language extension capabilities. For practical purposes, one is reminded of Racket’s general discouragement of assignments (see Guidelines for Using Assignment). For the alternative approach of functional references, also known as optics, see Lenses and Glass: Composable Optics.
The gref library combines gref/base and gref/syntax.
What does it mean for something to be stored? To account for this, imperative languages have long adopted the concept of l-values since Strachey (2000). For example, consider the following Algol 60 program:
beginintegerarrayintbox[0:0];intbox[0]:=1;printnln(intbox[0])end
Above, intbox[0] is an l-value that represents a location, and thus can be both read and write. The concept of locations is already defined in Racket (see Variables and Locations), so it is not difficult to imagine a concept similar to l-values. Indeed, generalized references, also known as places, are provided by Lisp Machine Lisp–inspired Lisps.
The concept of generalized references originates from Deutsch (1973), and has since been implemented for Lisp Machine Lisp, MacLisp, Common Lisp, and Emacs Lisp. For a detailed discussion on the history of generalized references, see Steele and Gabriel (1993). This section focuses on the technical aspects of generalized references.
The simplest implementation of generalized references is as in SRFI 17 , resembling the original proposal by Deutsch (1973) to an extent. That is, a getter procedure can be associated with a setter procedure, where
(procarg...)
corresponds to
((setterproc)arg...val)
such that setter maps the getter to setter. This is a simple yet elegant design. Unfortunately, this approach suffers from the fact that the setter must be dynamically resolved. Instead, Gref has adopted a full-blown macro approach similar to that of Lisp Machine Lisp, which allows for static resolution and more. In Gref, a fully-expanded generalized reference corresponding to some locations where some values are stored consists of four items:
An arity number for the number of values stored in the locations;
A getter procedure that accepts zero arguments and returns the values stored in the locations;
A setter procedure that accepts as many arguments as the arity and updates the locations;
A sequence of preamble forms that sets up the environment for the evaluation and validation of sub-expressions.
The preambles are supposed to precede any use of getter and setter within an internal-definition context, so that any introduced binding can be referred to. This way, the two modes of accesses, that is, reads and writes can be performed within the same context. In particular, multiple accesses can be performed at once while preserving the apparent left-to-right evaluation order (see Evaluation Order and Arity).
Another technical advantage is that a reference is allowed to represent multiple locations, including none. The arity determines the number of values stored in the locations. A well-behaved reference must arrange the getter and setter such that the former returns as many values as the arity, and the latter stores as many to the locations. The results of the setter are unspecified, but they should generally be #<void> following Racket’s convention (see Void and Undefined).
val)val)printing-unboxprinting-set-box!))unbox: outer
unbox: inner
set-box!: inner
unbox: outer
unbox: inner
'#hasheq((key . VAL))
Before the accesses are performed, the outer box is unbox ed exactly once and its content is validated to be box? . Then, the accesses are performed without unnecessary repeated evaluation. This capability further enables modify macros like shift! and rotate! .
The provide d set! expanders are defined through define-set!-syntax and make-set!-expander . All documented set! expanders preserve the apparent order of the sub-expressions and accordingly validate the results. When an inapproapriate result is detected, the exn:fail:contract exception is raise d.
syntax
(set! formvals)
vals : any
indicates that form is in a set! context, where the set! expander is invoked and produces a further expanded reference. A set! context is available in certain sub-form positions of set! expanders and modify macros where a reference is explicitly required. All documented set! expanders extend the base Racket procedures, and thus shadow the corresponding bindings in the default binding space as provide d by racket/base.
syntax
(set! ( string-ref stringpos)val)
syntax
(set! ( vector-ref vectorpos)val)
syntax
(set! ( vector*-ref vectorpos)val)
val : any/c
Modify macros are macros that operate on references. They are defined as usual macros, that is, (-> syntax? syntax? ) procedures. Unless otherwise stated, the result of a modify macro is always #<void>.
syntax
( set!-values pair...+)
vals : any
> mpair(mcons 2 2)
> mpair(mcons 2 1)
syntax
( pset!-values pair...+)
vals : any
> val1
> mpair(mcons 2 3)
syntax
( define-set!-syntax nameval)
syntax
( define-set!-syntaxes (name...)vals)
name = idvals : any
Returns a fully-expanded number-valued reference. The first two by-position arguments are the getter procedure and setter procedure, and the remaining by-position arguments are the preamble forms. The resulting syntax object is given the source-location information of src.
value
A structure type property to identify structure types that act as set! expanders used by gref . The property value takes the structure instance that implements prop:set!-expander and results in a set! expander, which in turn takes the syntax object of a number-valued reference and results in another number-valued reference.
procedure
( set!-expander? val)→boolean?
val:any/c
Returns #t if val implements prop:set!-expander , #f otherwise.
procedure
( make-set!-expander proc)→set!-expander?
Returns an implementation of prop:set!-expander that uses proc as the set! expander.
Returns an implementation of prop:set!-expander that expands functional forms. A functional form with the shape (whoarg...) where who’s transformer binding is the resulting implementation will be expanded such that:
Each expression in arg is evaluated in the apparent order and bound to arg-val;
A sequence of number identifiers val... is generated;
The expressions (lambda ()(getterarg-val...)) and (lambda (val...)(setterarg-val...val...)) are used as the getter and setter.
In other words, it works as a static alternative to SRFI 17, generalized to multiple values.
value
A flat contract that accepts an expected arity, where #f means any arity.
Equivalent to (or/c #fexact-nonnegative-integer? ).
syntax class
( gref [#:aritynumber])→syntax class
Matches a number-valued reference. If number is #f, matches any reference. The reference is recursively expanded until fully expanded as follows:
If it is a valid set!-pack ed form, the expansion is complete;
If it is an identifier with a transformer binding or a syntax-object pair whose first element is such an identifier, the transformer binding is used to continue;
Otherwise, if it is an identifier, a set! expander for variables is used to continue.
Each transformer binding is resolved in the 'gref/set! binding space. Due to the way scope sets works, a transformer binding in the default binding space will be used unless another transformer binding in the 'gref/set! binding space shadows it.
If the transformer binding refers to a set!-expander? value, it is used to produce a set! expander, which in turn receives the syntax object of the reference expanded so far. Otherwise, the matching fails and no further expansion is done. During each expansion step, scopes and syntax properties are accoridingly manipulated.
From the resulting set!-pack ed form, the following attributes are bound:
syntax class
( generalized-reference [#:aritynumber])→syntax class
An alias for gref .
procedure
( get-set!-expansion ref-stx[#:aritynumber])
ref-stx:syntax?
The procedural interface for gref . Expands ref-stx as a (gref #:aritynumber) form and returns the bound attributes in the documented order.
If syntax-transforming? returns #f, the exn:fail:contract exception is raise d and no expansion is done.
L. Peter Deutsch. A LISP machine with very compact programs. In Proc. International Joint Conference on Artificial Intelligence, pp. 697–703, 1973.
Guy L. Steele Jr. and Richard P. Gabriel. The evolution of Lisp. In Proc. Conference on History of Programming Languages, pp. 231–270, 1993. doi:10/dsp6sr
Christopher S. Strachey. Fundamental concepts in programming languages. Higher-Order and Symbolic Computation 13(1/2), pp. 11–49, 2000. doi:10/cpt37d
accesses
arity number
bytes-ref
call!
call2!
dec!
define-set!-syntax
define-set!-syntaxes
evaluation order
expanded
fully-expanded
functional forms
functional references
generalized reference
generalized-reference
get-set!-expansion
getter procedure
gref
gref
gref/base
'gref/set!
gref/syntax
Gref: Generalized References for Racket
hash-ref
inc!
Introduction
l-values
locations
make-set!-expander
make-set!-functional
maybe-arity/c
mcar
mcdr
Modify Macros
Modify macros
mpop!
mpush!
parallelly
pop!
preamble forms
prop:set!-expander
pset!
pset!-values
push!
represent
rotate!
sequentially
set!
set! context
set! Expanders
set! expanders
set!-expander?
set!-pack
set!-values
setter procedure
shift!
SRFI 17
stored
string-ref
The Base Module
The Syntax Module
unbox
unbox*
values
values
vector*-ref
vector-ref
well-behaved