Gref:   Generalized References for Racket
1 Introduction
2 The Base Module
2.1 set! Expanders
values
mcar
mcdr
hash-ref
bytes-ref
string-ref
vector-ref
vector*-ref
unbox
unbox*
2.2 Modify Macros
set!
set!-values
pset!
pset!-values
shift!
rotate!
call!
call2!
inc!
dec!
push!
mpush!
pop!
mpop!
3 The Syntax Module
define-set!-syntax
define-set!-syntaxes
set!-pack
prop:  set!-expander
set!-expander?
make-set!-expander
make-set!-functional
maybe-arity/  c
gref
generalized-reference
get-set!-expansion
Bibliography
Index
8.12

Gref: Generalized References for Racket🔗ℹ

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.

 (require gref) package: gref-lib

The gref library combines gref/base and gref/syntax.

    1 Introduction

    2 The Base Module

      2.1 set! Expanders

      2.2 Modify Macros

    3 The Syntax Module

    Bibliography

    Index

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-expanded generalized reference corresponding to some locations where some values are stored consists of four items:

  1. An arity number for the number of values stored in the locations;

  2. A getter procedure that accepts zero arguments and returns the values stored in the locations;

  3. A setter procedure that accepts as many arguments as the arity and updates the locations;

  4. 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).

As an example, consider the modify macro call!:
> (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🔗ℹ

 (require gref/base) package: gref-lib
The base module provides predefined set! expanders and modify macros.

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 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 provided by racket/base.

syntax

(set! (values ref ...) vals)

 
ref = gref
 
  vals : any
Combines refs into a reference that stores as many values as there are refs. Correspondingly, the arity is the number of refs, the getter returns all stored values, the setter stores to all locations, and the preambles are collected in the apparent order.

syntax

(set! (mcar mpair) val)

 
  mpair : mpair?
  val : any/c
Represents the mcar of mpair.

syntax

(set! (mcdr mpair) val)

 
  mpair : mpair?
  val : any/c
Represents the mcdr of mpair.

syntax

(set! (hash-ref hash key failure) val)

 
  hash : hash?
  key : any/c
  failure : (if/c procedure? (-> any/c) any/c)
  val : any/c
Represents the association for key in hash. If no such association is found, the result of failure is used. The setter further requires hash to be (not/c immutable?).

syntax

(set! (bytes-ref bytes pos) val)

 
  bytes : bytes?
  pos : exact-nonnegative-integer?
  val : byte?
Represents the posth position of bytes. The setter further requires bytes to be (not/c immutable?).

syntax

(set! (string-ref string pos) val)

 
  string : string?
  pos : exact-nonnegative-integer?
  val : char?
Represents the posth position of string. The setter further requires string to be (not/c immutable?).

syntax

(set! (vector-ref vector pos) val)

 
  vector : vector?
  pos : exact-nonnegative-integer?
  val : any/c
Represents the posth position of vector. The setter further requires vector to be (not/c immutable?).

syntax

(set! (vector*-ref vector pos) val)

 
  vector : (and/c vector? (not/c impersonator?))
  pos : exact-nonnegative-integer?
  val : any/c
Like vector-ref, but constrained to (not/c impersonator?) vectors.

syntax

(set! (unbox box) val)

 
  box : box?
  val : any/c
Represents the content of box. The setter further requires box to be (not/c immutable?).

syntax

(set! (unbox* box) val)

 
  box : (and/c box? (not/c impersonator?))
  val : any/c
Like unbox, but constrained to (not/c impersonator?) boxes.

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! pair ...+)

 
pair = ref vals
     
ref = (gref #:arity #f)
 
  vals : any
Stores the results of valss to refs sequentially in the apparent order.

syntax

(set!-values pair ...+)

 
pair = (ref ...) vals
     
ref = gref
 
  vals : any
Like set!, but constrained to multiple grefs.

syntax

(pset! pair ...+)

 
pair = ref vals
     
ref = (gref #:arity #f)
 
  vals : any
Like set!, but evaluates all valss parallelly before storing to refs in an unspecified order.

Examples:
> (define mpair (mcons 1 2))
> (set! (mcar mpair) (mcdr mpair)
        (mcdr mpair) (mcar mpair))
> mpair

(mcons 2 2)

vs.
> (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
Like pset!, but constrained to multiple grefs.

syntax

(shift! ref0 ref ... vals)

 
ref0 = (gref #:arity #f)
     
ref = (gref #:arity number)
 
  vals : any
Shifts from right to left, that is, stores the values from the n+1th reference (including vals as if it were one) to the nth reference. The arity of ref0 is used as number.

Examples:
> (define mpair (mcons 1 2))
> (shift! (mcar mpair) (mcdr mpair) 3)

1

> mpair

(mcons 2 3)

syntax

(rotate! ref0 ref ...+)

 
ref0 = (gref #:arity #f)
     
ref = (gref #:arity number)
Rotates from right to left (wrapping around), that is, stores the values from the n+1th (modulo n) reference to the nth reference. The arity of ref0 is used as number.

Examples:
> (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?
Applies (see Function Calls) proc to the value stored in ref and args, then stores the result to ref. The form of args is as in #%app. As in send, apply is used when a non-parenthesized expression arg-list-expr is present, otherwise #%app is used.

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?
Like call!, but with arg0-expr as the first non-keyword argument.

syntax

(inc! ref maybe-delta)

 
ref = gref
     
maybe-delta = 
  | delta
 
  delta : number?
Increments the value stored in ref by delta, which defaults to 1. That is, it is equivalent to (call! + ref delta).

syntax

(dec! ref maybe-delta)

 
ref = gref
     
maybe-delta = 
  | delta
 
  delta : number?
Like inc!, but uses - to decrement.

syntax

(push! val ref)

 
ref = gref
 
  val : any/c
Prepends val to the value stored in ref. That is, it is equivalent to (call2! cons val ref).

syntax

(mpush! val ref)

 
ref = gref
 
  val : any/c
Like push!, but uses mcons.

syntax

(pop! ref)

 
ref = gref
Takes the cdr of the value stored in ref and stores the result back to ref. Returns the car of the value originally stored in ref.

syntax

(mpop! ref)

 
ref = gref
Like pop!, but uses mcar and mcdr.

3 The Syntax Module🔗ℹ

The syntax module provides various bindings useful for user extensions.

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
Like define-syntax, but the created binding is in the 'gref/set! binding space.

syntax

(define-set!-syntaxes (name ...) vals)

 
name = id
 
  vals : any
Like define-syntaxes, but the created bindings are in the 'gref/set! binding space.

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
Exported for-syntax.

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.

Exported for-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-valued reference and results in another number-valued reference.

procedure

(set!-expander? val)  boolean?

  val : any/c
Exported for-syntax.

Returns #t if val implements prop:set!-expander, #f otherwise.

procedure

(make-set!-expander proc)  set!-expander?

  proc : (-> syntax? syntax?)
Exported for-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
Exported for-syntax.

Returns an implementation of prop:set!-expander that expands functional forms. A functional form with the shape (who arg ...) where who’s transformer binding is the resulting implementation will be expanded such that:

In other words, it works as a static alternative to SRFI 17, generalized to multiple values.

Exported for-syntax.

A flat contract that accepts an expected arity, where #f means any arity.

Equivalent to (or/c #f exact-nonnegative-integer?).

syntax class

(gref [#:arity number])  syntax class

  number : maybe-arity/c = 1
Exported for-syntax.

Matches a number-valued reference. If number is #f, matches any reference. The reference is recursively expanded until fully expanded as follows:

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!-packed form, the following attributes are bound:

attribute

arity : exact-nonnegative-integer?

attribute

getter : syntax?

attribute

setter : syntax?

attribute

preamble : (listof syntax?)

If syntax-transforming? returns #f, the matching fails and no expansion is done.

syntax class

(generalized-reference [#:arity number])  syntax class

  number : maybe-arity/c = 1
Exported for-syntax.

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
Exported for-syntax.

The procedural interface for gref. Expands ref-stx as a (gref #:arity number) 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

Index🔗ℹ

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

 

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