Gref: Generalized References for Racket
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.
1 Introduction
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:
begin integer array intbox[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
(proc arg ...)
corresponds to
((setter proc) 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-expandedgeneralized reference corresponding to some locations where some values are stored consists of four items:
An arity number for the number of valuesstored in the locations;
A getter procedure that accepts zero arguments and returns the valuesstored 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 valuesstored in the locations. A well-behavedreference 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).
> (define (printing-box val #:name name) (define (printing-unbox bx val) (printf "unbox: ~a\n" name) val) (define (printing-set-box! bx val) (printf "set-box!: ~a\n" name) val) (chaperone-box (box val) printing-unbox printing-set-box!))
> (define box-of-box (printing-box (printing-box #hasheq((key . "val")) #:name 'inner) #:name 'outer))
> (call! hash-update (unbox (unbox box-of-box)) 'key (compose1 string->symbol string-upcase))
unbox: outer
unbox: inner
set-box!: inner
> (unbox (unbox box-of-box))
unbox: outer
unbox: inner
'#hasheq((key . VAL))
Before the accesses are performed, the outer box is unboxed 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!.
2 The Base Module
2.1 set! Expanders
The provided 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 raised.
syntax
(set! form vals)
vals : any
indicates that form is in a set! context, where the set! expander is invoked and produces a further expandedreference. 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 provided by racket/base.
syntax
bytes : bytes?
pos : exact-nonnegative-integer?
val : byte?
syntax
(set! (string-ref string pos) val)
string : string?
pos : exact-nonnegative-integer?
val : char?
syntax
(set! (vector-ref vector pos) val)
vector : vector?
pos : exact-nonnegative-integer?
val : any/c
syntax
(set! (vector*-ref vector pos) val)
vector : (and/c vector? (not/c impersonator?))
pos : exact-nonnegative-integer?
val : any/c
2.2 Modify Macros
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 ...+)
pair = (ref ...) vals ref = gref
vals : any
> (define mpair (mcons 1 2))
> (set! (mcar mpair) (mcdr mpair) (mcdr mpair) (mcar mpair)) > mpair (mcons 2 2)
> (define mpair (mcons 1 2))
> (pset! (mcar mpair) (mcdr mpair) (mcdr mpair) (mcar mpair)) > mpair (mcons 2 1)
syntax
(pset!-values pair ...+)
pair = (ref ...) vals ref = gref
vals : any
> (define mpair (mcons 1 2)) > (define val 3) > (rotate! (mcar mpair) (mcdr mpair) val) > val 1
> mpair (mcons 2 3)
syntax
(call! proc ref arg ...)
(call! proc ref arg ... . arg-list-expr)
ref = gref arg = keyword arg-expr | arg-expr
proc : (unconstrained-domain-> any/c)
arg-expr : any/c
arg-list-expr : list?
syntax
(call2! proc arg0-expr ref arg ...)
(call2! proc arg0-expr ref arg ... . arg-list-expr)
ref = gref arg = keyword arg-expr | arg-expr
proc : (unconstrained-domain-> any/c)
arg0-expr : any/c
arg-expr : any/c
arg-list-expr : list?
3 The Syntax Module
(requiregref/syntax) | package:gref-lib |
syntax
(define-set!-syntax name val)
(define-set!-syntax header body ...+)
name = id header = (header args) | (name args) args = arg ... | arg ... . id arg = keyword id-or-id+expr | id-or-id+expr id-or-id+expr = id | [id default-expr]
val : any/c
default-expr : any/c
syntax
(define-set!-syntaxes (name ...) vals)
name = id
vals : any
procedure
(set!-pack getter setter preamble ... [ #:arity number #:source src]) → syntax? getter : syntax? setter : syntax? preamble : syntax? number : exact-nonnegative-integer? = 1 src : source-location? = #f
Returns a fully-expandednumber-valuedreference. 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
prop:set!-expander :
(struct-type-property/c (-> set!-expander? (-> syntax? syntax?)))
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-valuedreference and results in another number-valuedreference.
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?
proc : (-> syntax? syntax?)
Returns an implementation of prop:set!-expander that uses proc as the set! expander.
procedure
(make-set!-functional getter setter [ #:arity number]) → syntax? getter : syntax? setter : syntax? number : exact-nonnegative-integer? = 1
Returns an implementation of prop:set!-expander that expandsfunctional 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 numberidentifiersval... 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 [#:arity number]) → syntax class
number : maybe-arity/c = 1
Matches a number-valuedreference. If number is #f, matches any reference. The reference is recursively expanded until fully expanded as follows:
If it is a valid set!-packed 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 spaceshadows 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 referenceexpanded 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!-packed form, the following attributes are bound:
attribute
arity : exact-nonnegative-integer?
attribute
getter : syntax?
attribute
setter : syntax?
attribute
syntax class
(generalized-reference [#:arity number]) → syntax class
number : maybe-arity/c = 1
An alias for gref.
procedure
(get-set!-expansion ref-stx [#:arity number])
→
exact-nonnegative-integer? syntax? syntax? (listof syntax?) ref-stx : syntax? number : maybe-arity/c = 1
The procedural interface for gref. Expandsref-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 raised and no expansion is done.
Bibliography
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 |