8.18
top
← prev up next →

Gref: Generalized References for RacketπŸ”— i

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.

1IntroductionπŸ”— i

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
integerarrayintbox[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:

  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-boxval#:namename)
(define (printing-unboxbxval)
(printf "unbox: ~a\n"name)
val)
(define (printing-set-box!bxval)
(printf "set-box!: ~a\n"name)
val)
printing-unbox
printing-set-box!))
> (define box-of-box
(printing-box(printing-box#hasheq((key. "val"))
#:name'inner)
#:name'outer))

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

2The Base ModuleπŸ”— i

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

2.1set! ExpandersπŸ”— i

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! ( 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 hashkeyfailure)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 bytespos)val)

Represents the posth position of bytes. The setter further requires bytes to be (not/c immutable? ).

syntax

(set! ( string-ref stringpos)val)

Represents the posth position of string. The setter further requires string to be (not/c immutable? ).

syntax

(set! ( vector-ref vectorpos)val)

Represents the posth position of vector. The setter further requires vector to be (not/c immutable? ).

syntax

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

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)

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

2.2Modify MacrosπŸ”— i

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 = refvals
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 gref s.

syntax

( pset! pair...+)

pair = refvals
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 12))
> (set! (mcar mpair)(mcdr mpair)
(mcdr mpair)(mcar mpair))
> mpair

(mcons 2 2)

vs.
> (define mpair(mcons 12))
> (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 gref s.

syntax

( shift! ref0ref...vals)

ref0 = (gref #:arity#f)
ref = (gref #:aritynumber)
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 12))
> (shift! (mcar mpair)(mcdr mpair)3)

1

> mpair

(mcons 2 3)

syntax

( rotate! ref0ref...+)

ref0 = (gref #:arity#f)
ref = (gref #:aritynumber)
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 12))
> (define val3)
> (rotate! (mcar mpair)(mcdr mpair)val)
> val

1

> mpair

(mcons 2 3)

syntax

( call! procrefarg...)

(call! procrefarg.... arg-list-expr)
ref = gref
arg = keyword arg-expr
| arg-expr
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! procarg0-exprrefarg...)

(call2! procarg0-exprrefarg.... arg-list-expr)
ref = gref
arg = keyword arg-expr
| arg-expr
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! refmaybe-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! + refdelta).

syntax

( dec! refmaybe-delta)

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

syntax

( push! valref)

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

syntax

( mpush! valref)

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 .

3The Syntax ModuleπŸ”— i

The syntax module provide s various bindings useful for user extensions.

syntax

( define-set!-syntax nameval)

(define-set!-syntax headerbody...+)
name = id
header = (headerargs)
| (nameargs)
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...
[ #:aritynumber
#:sourcesrc])syntax?
getter:syntax?
setter:syntax?
preamble:syntax?
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

setter
[ #:aritynumber])syntax?
getter:syntax?
setter:syntax?
Exported for-syntax .

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.

Exported for-syntax .

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

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

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 [#:aritynumber])syntax class

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

An alias for gref .

procedure

( get-set!-expansion ref-stx[#:aritynumber])

ref-stx:syntax?
number:maybe-arity/c =1
Exported for-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.

BibliographyπŸ”— i

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πŸ”— i

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

top
← prev up next →

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /